Configuring conform.nvim with Elixir: A Guide for Elixirists

Are you using the Conform formatter for Neovim (nvim) alongside your Elixir projects? If so, this guide is for you. Specifically, we’ll cover how to ensure that Conform uses the correct current working directory (CWD) when formatting files in an Elixir project built as an umbrella project.

What is Conform and Why Use it?

Conform is a formatter for nvim, designed to make your code look consistent and readable. It’s often used in conjunction with Neovim, which provides many features to improve the coding experience. By setting up Conform, you can ensure that your Elixir code is formatted consistently across all projects.

What is an Elixir Umbrella Project?

In Elixir, an umbrella project is a “mono-repo” where multiple projects are organized under a single root directory. This allows you to run commands like mix test in the root directory and have it execute tests for all sub-projects. The root directory typically contains files such as mix.exs, .formatter.exs, and others.

The Issue with Conform

When using Conform to format Elixir files, you may encounter an issue where it uses the wrong CWD. By default, Conform will use the CWD of the current file being formatted. In the case of umbrella projects, this can lead to unexpected behavior and formatting errors.

This is due to mismatch with the mix format itself. Conforms uses the mix.lua, which itself is just a wrapper for mix format -. The dash at the end means it uses standard input and output.

The issue is when you run mix format in the sub-project, it will still run format on the other projects. And if you have any warnings or such it will print them out. Which Conform won’t expect, leading it to append parts of the warning at the top of the file.

The symptom you’ll notice is that you’ll get an syntax error and a line that looks like this in all files you format:

==> name_of_project_with_warning

This issue can be frustrating, and can take a bit of digging around to figure out where it comes from.

The debugging

I updated my command to run a wrapper I wrote to easier debug what was happening. I updated the setup to:

require("conform").setup({
  formatters_by_ft = {
    elixir = {"mix"},
  },
  formatters = {
    mix = {
		cmd = "/path/to/my/script/wrapper.sh"
    },
  },
})

Here is the script wrapper.sh:

#!/bin/bash
CWD_LOG=/tmp/cwd.log
INPUT=/tmp/input.log
OUTPUT=/tmp/output.log

cat - > $INPUT 
cat $INPUT | mix format - > $OUTPUT
echo "CWD: $(pwd)" >> $CWD_LOG

It reads from standard input, stores it in /tmp/input.log. It calls the mix format -, stores the output in /tmp/output.log. There no difference in input, but it gave different output. I added the log of current work directory. By reading the /tmp/cwd.log I saw i was the issue due to mix being called from a sub-project.

The Solution: Configuring Conform

To fix this issue, we need to configure Conform to use the correct CWD. We can do this by providing a function that takes an array of files as input. This function will iterate up the file tree until it finds the specified files. In our case, we can specify apps/ as the files (directory) to look for. This will only work in umbrella projects though. Using the .git/ folder might work better across different Elixir projects.

The complete configuration for Conform is as follows:

local options = {
  lsp_fallback = true,
  log_level = vim.log.levels.ERROR,

  formatters_by_ft = {
    elixir = {"mix"},
  },

  formatters = {
    mix = {
      cwd = require("conform.util").root_file({
        # Choose either or both depending on your needs.
        "apps/",
        ".git/",
      }),
    },
  },
}

require("conform").setup(options)

By following these steps, you should be able to configure Conform to use the correct CWD when formatting Elixir files in an umbrella project. Happy coding!