/vim-filetype-formatter

Format program files in vim using your favorite command line formatter

Primary LanguageVim ScriptMIT LicenseMIT

Vim Filetype Formatter

A simple, cross-language Vim code formatter plugin supporting both range and full-file formatting.

It uses code formatters; it does not install them.

See our pre-configured languages and formatters. Don't like the defaults? Writing your own is easy! Each Vim filetype maps to one command. This plugin supports compatible Vim commands, or any command line code formatter as long as it:

  1. Reads from standard input.
  2. Writes to standard output.
  3. Is in your $PATH.

Requires Bash and a recent version of Vim or Neovim.

Differentiating Features

  • Respects configuration files (pyproject.toml, .rustfmt.toml, .prettierrc.toml, etc.)
  • Accepts visually-selected ranges for any formatter
  • Preserves Vim cursor location after the formatter has run
  • Clear logging, so you can see why a formatter is or isn't working (:LogFiletypeFormat)
  • Easy debugging of user configuration (:DebugFiletypeFormat)
  • Chain formatters together with Unix pipes
  • Configurable, with sane defaults
  • Simple, extendable codebase
  • Modular: does not pollute your Vim environment with custom key mappings / poor Vim plugin practices

Screencast

The following screencast demonstrates :FiletypeFormat, :LogFiletypeFormat, and :DebugFiletypeFormat.

Screencast

Configuration Overview

Although black works out of the box for Python, the above example overrides the default and combines black with isort and docformatter using Unix pipes. This specific example can be achieved with the following configuration in your vimrc or init.vim:

let g:vim_filetype_formatter_commands = {
      \ 'python': 'black -q - | isort -q - | docformatter -',
      \ }

For further customization (e.g., where you need anything dynamic), you can pass either a Funcref or a lambda expression. For example, you might want to pass the current filename as an argument to your command line program. Here is an example for Python using a lambda expression:

let g:vim_filetype_formatter_commands = {
      \ 'python': {-> printf('black -q --stdin-filename="%1$s" - | isort -q --filename="%1$s" - | docformatter -', expand('%:p'))},
      \ }

Here's another Python example involving ruff.

function s:formatter_python()
  return printf(
        \ 'ruff check --unsafe-fixes -q --fix-only --stdin-filename="%1$s" - | ' ..
        \ 'ruff format -q --stdin-filename="%1$s" -',
        \ expand('%:p'))
endfunction
let g:vim_filetype_formatter_commands = {'python': function('s:formatter_python')}

Here's an example of how we can support prettier's built-in range functionality:

function! s:prettier(startline, endline)
  return printf(
        \ 'npx --no-update-notifier --silent --no-install prettier --range-start=%i --range-end=%i --stdin-filepath="%s"',
        \ line2byte(a:startline) - 1,
        \ line2byte(a:endline + 1) - 1,
        \ expand('%:p')
        \ )
endfunction
let g:vim_filetype_formatter_commands = {'javascript': function('s:prettier')}

Finally, custom Vim commands may be used instead of shell commands by ensuring that your final string is prefixed with a :. For an example implementation, see here:

" Use vim's built-in commands.
" 1. = (the vimscript_builtin)
" 2. Replace all instances of multiple blank lines, shortening to a single
function! s:vimscript_builtin(startline, endline)
  return printf(
        \ ':silent! execute "normal! %igg=%igg" | silent! %i,%iglobal/^\_$\n\_^$/de',
        \ a:startline, a:endline,
        \ a:startline, a:endline
        \ )
endfunction
let g:vim_filetype_formatter_commands = {'vim': function('s:vimscript_builtin')}

Installation

If using vim-plug, place the following line in the Plugin section of your init.vim / vimrc:

Plug 'pappasam/vim-filetype-formatter'

Then run the Ex command:

:PlugInstall

I personally use vim-packager, so if you'd like to go down the "package" rabbit hole, I suggest giving that a try.

Full Documentation

From within Vim, type:

:help filetype_formatter

Key mappings

This plugin provides no default key mappings. I recommend setting a key mapping for normal mode and visual mode like this:

nnoremap <silent> <leader>f <Cmd>FiletypeFormat<CR>
xnoremap <silent> <leader>f :FiletypeFormat<CR>

Default configurations

Default configurations may be overridden by creating our own g:vim_filetype_formatter_commands dictionary. If you would like to map one filetype to another, see g:vim_filetype_formatter_ft_maps. See here for specifics on how to do this.

If you would like to use a formatter listed above in "Other Formatters", you'll first need to packadd vim-filetype-formatter and then add it to g:vim_filetype_formatter commands. Here is an example of how to override Python's formatter with the built-in configuration for ruff:

packadd vim-filetype-formatter
let g:vim_filetype_formatter_commands.python = g:vim_filetype_formatter_builtins.ruff

Non-standard code formatters

In the rare case where a required code formatter does not read from standard input and/or write to standard output, don't panic. With some effort, you can probably still create a working command by chaining the code formatter with standard Unix programs. See the following example, using nginxbeautifier:

\ 'nginx':
\   'dd status=none of=/tmp/nginx.conf >& /dev/null && '
\   .. 'nginxbeautifier --space 4 /tmp/nginx.conf >& /dev/null && '
\   .. 'cat /tmp/nginx.conf && '
\   .. 'rm /tmp/nginx.conf',
  1. dd: read vim-filetype-formatter's standard output as standard input, writing to a temporary file named /tmp/nginx.conf
  2. nginxbeautifier: read from the temporary file and modify that file in-place
  3. cat: write the contents of the temporary file to stdout
  4. rm: remove the temporary file to keep things tidy

It's not exactly pretty, but:

  1. Reality isn't always pretty
  2. We can use the command because it reads from standard input and writes to standard output

Batteries Included

Language Default Formatter Other Formatters
bash/sh shfmt
biblatex bibtool
css prettier
dockerfile vim.lsp.buf.format
dosini built-in
gitconfig built-in
go gofmt
graphql prettier
html prettier
htmldjango prettier_jinja
javascript/jsx prettier
jinja.html prettier_jinja
json prettier
jsonc prettier
lua stylua
make built-in
markdown prettier
mdx prettier
nginx nginxfmt
ocaml ocamlformat
prisma prettier_prisma
python black ruff
r styler
rust rustfmt leptosfmt
scss prettier
svelte prettier_svelte
terraform terraform_fmt
toml taplo
typescript/tsx prettier
vimscript built-in
yaml prettier
zsh built-in

FAQ

How can I use an executable from my project's node_modules/ folder?

For example, if you have a different version of prettier installed in your project than you installed globally, you'll probably want vim-filetype-formatter to use your project's version of prettier. To achieve this:

  1. Place the following line in init.vim / .vimrc:
    let $PATH = $PWD .. '/node_modules/.bin:' .. $PATH
  2. Open Neovim at the root of your project.
  3. You should now be referencing executable files within your project's node_modules/ folder.

How can I have per-project settings?

If using a recent version of Neovim, see :help 'exrc'.

" $XDG_CONFIG_HOME/init.vim
set exrc
" $PROJECT_PATH/.nvimrc
packadd vim-filetype-formatter
let g:vim_filetype_formatter_commands['python'] = g:vim_filetype_formatter_builtins['ruff']
let g:vim_filetype_formatter_commands['rust'] = g:vim_filetype_formatter_builtins['leptosfmt']