Skip to content

Commit

Permalink
feat!: merge configs in conform.formatters with defaults (#140)
Browse files Browse the repository at this point in the history
This breaking change should make it significantly easier to modify formatters. While I expect 99% of configs to be backwards-compatible, this can still potentially cause problems. If you:

* define a formatter in the `formatters` option
* that has the same name as a built-in formatter
* and omits a property from the original formatter (e.g. leaves out `range_args` or `cwd`)

Then you may encounter breaking behavior from this commit, because now your config definition will be merged with the built-in definition, and so will inherit those omitted properties. This config merging behavior can be opted-out of by adding `inherit = false` to your formatter config.
  • Loading branch information
stevearc authored Oct 15, 2023
1 parent 9b5fbdd commit 7027ebb
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 156 deletions.
189 changes: 132 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ Lightweight yet powerful formatter plugin for Neovim
- [Installation](#installation)
- [Setup](#setup)
- [Formatters](#formatters)
- [Options](#options)
- [Customizing formatters](#customizing-formatters)
- [Recipes](#recipes)
- [Advanced topics](#advanced-topics)
- [Options](#options)
- [API](#api)
- [format(opts, callback)](#formatopts-callback)
- [list_formatters(bufnr)](#list_formattersbufnr)
Expand All @@ -30,9 +31,9 @@ Lightweight yet powerful formatter plugin for Neovim

- **Preserves extmarks and folds** - Most formatters replace the entire buffer, which clobbers extmarks and folds, and can cause the viewport and cursor to jump unexpectedly. Conform calculates minimal diffs and applies them using the built-in LSP format utilities.
- **Fixes bad-behaving LSP formatters** - Some LSP servers are lazy and simply replace the entire buffer, leading to the problems mentioned above. Conform hooks into the LSP handler and turns these responses into proper piecewise changes.
- **Enables range formatting for all formatters** - Since conform calculates minimal diffs, it can perform range formatting even if the underlying formatter doesn't support it.
- **Enables range formatting for all formatters** - Since conform calculates minimal diffs, it can perform range formatting [even if the underlying formatter doesn't support it.](doc/advanced_topics.md#range-formatting)
- **Simple API** - Conform exposes a simple, imperative API modeled after `vim.lsp.buf.format()`.
- **Formats embedded code blocks** - Use the `injected` formatter to format code blocks e.g. in markdown files.
- **Formats embedded code blocks** - Can format code blocks inside markdown files or similar (see [injected language formatting](doc/advanced_topics.md#injected-language-formatting-code-blocks))

## Installation

Expand All @@ -48,17 +49,21 @@ conform.nvim supports all the usual plugin managers
}
```

For a more thorough configuration involving lazy-loading, see [Lazy loading with lazy.nvim](doc/recipes.md#lazy-loading-with-lazynvim).

</details>

<details>
<summary>Packer</summary>

```lua
require('packer').startup(function()
use {
'stevearc/conform.nvim',
config = function() require('conform').setup() end
}
require("packer").startup(function()
use({
"stevearc/conform.nvim",
config = function()
require("conform").setup()
end,
})
end)
```

Expand All @@ -68,9 +73,9 @@ end)
<summary>Paq</summary>

```lua
require "paq" {
{'stevearc/conform.nvim'};
}
require("paq")({
{ "stevearc/conform.nvim" },
})
```

</details>
Expand Down Expand Up @@ -118,36 +123,36 @@ At a minimum, you will need to set up some formatters by filetype

```lua
require("conform").setup({
formatters_by_ft = {
lua = { "stylua" },
-- Conform will run multiple formatters sequentially
python = { "isort", "black" },
-- Use a sub-list to run only the first available formatter
javascript = { { "prettierd", "prettier" } },
},
formatters_by_ft = {
lua = { "stylua" },
-- Conform will run multiple formatters sequentially
python = { "isort", "black" },
-- Use a sub-list to run only the first available formatter
javascript = { { "prettierd", "prettier" } },
},
})
```

Then you can use `conform.format()` just like you would `vim.lsp.buf.format()`. For example, to format on save:

```lua
vim.api.nvim_create_autocmd("BufWritePre", {
pattern = "*",
callback = function(args)
require("conform").format({ bufnr = args.buf })
end,
pattern = "*",
callback = function(args)
require("conform").format({ bufnr = args.buf })
end,
})
```

As a shortcut, conform will optionally set up this format-on-save autocmd for you

```lua
require("conform").setup({
format_on_save = {
-- These options will be passed to conform.format()
timeout_ms = 500,
lsp_fallback = true,
},
format_on_save = {
-- These options will be passed to conform.format()
timeout_ms = 500,
lsp_fallback = true,
},
})
```

Expand All @@ -165,6 +170,9 @@ To view configured and available formatters, as well as to see the log file, run

You can view this list in vim with `:help conform-formatters`

<details>
<summary>Expand to see all formatters</summary>

<!-- FORMATTERS -->

- [alejandra](https://kamadorueda.com/alejandra/) - The Uncompromising Nix Code Formatter.
Expand Down Expand Up @@ -255,6 +263,92 @@ You can view this list in vim with `:help conform-formatters`
- [zigfmt](https://github.com/ziglang/zig) - Reformat Zig source into canonical form.
<!-- /FORMATTERS -->

</details>

## Customizing formatters

You can override/add to the default values of formatters

```lua
require("conform").setup({
formatters = {
yamlfix = {
-- Change where to find the command
command = "local/path/yamlfix",
-- Adds environment args to the yamlfix formatter
env = {
YAMLFIX_SEQUENCE_STYLE = "block_style",
},
},
},
})

-- These can also be set directly
require("conform").formatters.yamlfix = {
env = {
YAMLFIX_SEQUENCE_STYLE = "block_style",
},
}

-- This can also be a function that returns the config,
-- which can be useful if you're doing lazy loading
require("conform").formatters.yamlfix = function(bufnr)
return {
command = require("conform.util").find_executable({
"local/path/yamlfix",
}, "yamlfix"),
}
end
```

In addition to being able to override any of the original properties on the formatter, there is another property for easily adding additional arguments to the format command

```lua
require("conform").formatters.shfmt = {
prepend_args = { "-i", "2" },
-- The base args are { "-filename", "$FILENAME" } so the final args will be
-- { "-i", "2", "-filename", "$FILENAME" }
}
-- prepend_args can be a function, just like args
require("conform").formatters.shfmt = {
prepend_args = function(ctx)
return { "-i", "2" }
end,
}
```

If you want to overwrite the entire formatter definition and _not_ merge with the default values, pass `inherit = false`. This is also the default behavior if there is no built-in formatter with the given name, which can be used to add your own custom formatters.

```lua
require("conform").formatters.shfmt = {
inherit = false,
command = "shfmt",
args = { "-i", "2", "-filename", "$FILENAME" },
}
```

## Recipes

<!-- RECIPES -->

- [Format command](doc/recipes.md#format-command)
- [Autoformat with extra features](doc/recipes.md#autoformat-with-extra-features)
- [Command to toggle format-on-save](doc/recipes.md#command-to-toggle-format-on-save)
- [Automatically run slow formatters async](doc/recipes.md#automatically-run-slow-formatters-async)
- [Lazy loading with lazy.nvim](doc/recipes.md#lazy-loading-with-lazynvim)

<!-- /RECIPES -->

## Advanced topics

<!-- ADVANCED -->

- [Minimal format diffs](doc/advanced_topics.md#minimal-format-diffs)
- [Range formatting](doc/advanced_topics.md#range-formatting)
- [Injected language formatting (code blocks)](doc/advanced_topics.md#injected-language-formatting-code-blocks)

<!-- /ADVANCED -->

## Options

A complete list of all configuration options
Expand Down Expand Up @@ -294,14 +388,14 @@ require("conform").setup({
log_level = vim.log.levels.ERROR,
-- Conform will notify you when a formatter errors
notify_on_error = true,
-- Define custom formatters here
-- Custom formatters and changes to built-in formatters
formatters = {
my_formatter = {
-- This can be a string or a function that returns a string
-- This can be a string or a function that returns a string.
-- When defining a new formatter, this is the only field that is *required*
command = "my_cmd",
-- OPTIONAL - all fields below this are optional
-- A list of strings, or a function that returns a list of strings
-- Return a single string instead to run the command in a shell
-- Return a single string instead of a list to run the command in a shell
args = { "--stdin-from-filename", "$FILENAME" },
-- If the formatter supports range formatting, create the range arguments here
range_args = function(ctx)
Expand All @@ -319,15 +413,20 @@ require("conform").setup({
condition = function(ctx)
return vim.fs.basename(ctx.filename) ~= "README.md"
end,
-- Exit codes that indicate success (default {0})
-- Exit codes that indicate success (default { 0 })
exit_codes = { 0, 1 },
-- Environment variables. This can also be a function that returns a table.
env = {
VAR = "value",
},
-- Set to false to disable merging the config with the base definition
inherit = true,
-- When inherit = true, add these additional arguments to the command.
-- This can also be a function, like args
prepend_args = { "--use-tabs" },
},
-- These can also be a function that returns the formatter
other_formatter = function()
other_formatter = function(bufnr)
return {
command = "my_cmd",
}
Expand All @@ -344,30 +443,6 @@ require("conform").formatters.my_formatter = {

<!-- /OPTIONS -->

## Recipes

<!-- RECIPES -->

- [Format command](doc/recipes.md#format-command)
- [Customizing formatters](doc/recipes.md#customizing-formatters)
- [Autoformat with extra features](doc/recipes.md#autoformat-with-extra-features)
- [Command to toggle format-on-save](doc/recipes.md#command-to-toggle-format-on-save)
- [Automatically run slow formatters async](doc/recipes.md#automatically-run-slow-formatters-async)
- [Add extra arguments to a formatter command](doc/recipes.md#add-extra-arguments-to-a-formatter-command)
- [Lazy loading with lazy.nvim](doc/recipes.md#lazy-loading-with-lazynvim)

<!-- /RECIPES -->

## Advanced topics

<!-- ADVANCED -->

- [Minimal format diffs](doc/advanced_topics.md#minimal-format-diffs)
- [Range formatting](doc/advanced_topics.md#range-formatting)
- [Injected language formatting (code blocks)](doc/advanced_topics.md#injected-language-formatting-code-blocks)

<!-- /ADVANCED -->

## API

<!-- API -->
Expand Down
17 changes: 11 additions & 6 deletions doc/conform.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ OPTIONS *conform-option
log_level = vim.log.levels.ERROR,
-- Conform will notify you when a formatter errors
notify_on_error = true,
-- Define custom formatters here
-- Custom formatters and changes to built-in formatters
formatters = {
my_formatter = {
-- This can be a string or a function that returns a string
-- This can be a string or a function that returns a string.
-- When defining a new formatter, this is the only field that is *required*
command = "my_cmd",
-- OPTIONAL - all fields below this are optional
-- A list of strings, or a function that returns a list of strings
-- Return a single string instead to run the command in a shell
-- Return a single string instead of a list to run the command in a shell
args = { "--stdin-from-filename", "$FILENAME" },
-- If the formatter supports range formatting, create the range arguments here
range_args = function(ctx)
Expand All @@ -69,15 +69,20 @@ OPTIONS *conform-option
condition = function(ctx)
return vim.fs.basename(ctx.filename) ~= "README.md"
end,
-- Exit codes that indicate success (default {0})
-- Exit codes that indicate success (default { 0 })
exit_codes = { 0, 1 },
-- Environment variables. This can also be a function that returns a table.
env = {
VAR = "value",
},
-- Set to false to disable merging the config with the base definition
inherit = true,
-- When inherit = true, add these additional arguments to the command.
-- This can also be a function, like args
prepend_args = { "--use-tabs" },
},
-- These can also be a function that returns the formatter
other_formatter = function()
other_formatter = function(bufnr)
return {
command = "my_cmd",
}
Expand Down
Loading

0 comments on commit 7027ebb

Please sign in to comment.