Neovim - Project Local Config with exrc.nvim
Tags: neovim, lua, vim
Published: 2022-08-11 16:47:00

Neovim is awesome and I love it!

Why? You might ask. The answer is rather simple.

It's awesome because I love it.

Now that we agree on that, let's focus on the topic... Project Local Config.

## Why is Project Local Config?

Usually your Neovim configuration lives in a single location: `~/.config/nvim`. And most of the times, that's all you need. But what if you need to change some config only for a specific project? Maybe you want to set a different theme when you're working on that project... or maybe tweak the behavior of the LSP server for that specific project.

That's why you need project local config for Neovim. It can be done in multiple ways and I'm gonna list them all.

### Modify your Neovim config file

This is the most straight-forward way of doing it. In your Neovim config file, you need check the current working directory and set the config based on that:

```lua
--[[ ~/.config/nvim/init.lua ]]

local cwd = vim.fn.getcwd(-1, -1)

if cwd == '/path/to/project-a' then
  -- config for project-a
elseif cwd = '/path/to/project-b' then
  -- config for project-b
end
```

**Pros**:

- Safe. It's your own config.
- Supports Lua config file.

**Cons**:

- Config can't be tracked with the project using `git`.
- Hardcoded paths are machine dependent.

If you're okay with this approach, you can stop here. But do continue if you want something better.

### Built-in Option: `exrc`

This is the built-in `'exrc'` option. If enabled, Neovim will search for the following files in the current directory:

- `.nvimrc`
- `.exrc`

And use the first one it finds.

Enabling it is as simple as adding this in your Neovim config:

```lua
--[[ ~/.config/nvim/init.lua ]]

vim.o.exrc = true
```

**Pros**:

- Built-in feature.
- Config can be tracked with the project using `git`.

**Cons**:

- Deprecated (check `:help 'exrc'`).
- Security risk. Config is run blindly.
- No support for Lua config file.

If you end up going with this approach, you should also enable the `'secure'` option. That will reduce the security risk of `'exrc'` option a bit.

```lua
--[[ ~/.config/nvim/init.lua ]]

vim.o.secure = true
```

But if you want one better, the next one is for you.

### Plugin: [**`exrc.nvim`**](https://github.com/MunifTanjim/exrc.nvim)

It's the plugin that I wrote to make `'exrc'` great again. It does everything the built-in `'exrc'` option does and then some more... and does all that safely.

You install it like any other plugin, using your plugin manager of choice. And the set it up like this:

```lua
--[[ ~/.config/nvim/init.lua ]]

vim.o.exrc = false

require("exrc").setup({
  files = {
    ".nvimrc.lua",
    ".nvimrc",
    ".exrc.lua",
    ".exrc",
  },
})
```

Now you can add local config file for your project:

```lua
--[[ /path/to/project-a ]]

print("This is awesome!")
```

And then, the first time you open this project, you'll be greeted with this prompt:

![exrc.nvim Prompt](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/acs9ncp2cygb7ujkcquy.png)

If you allow it, it will run the config file and remember your choice. If the config file changes, it will show the prompt again for you to review the updated content.

**Pros**:

- Secure. Config is run only after you review and allow it.
- Config can be tracked with the project using `git`.
- Supports Lua config file.

**Cons**:

- Once you use it, you can't do without it.

## Project Local LSP Settings with exrc.nvim

Now, let's see how you can use `exrc.nvim` to modify LSP settings for a specific project.

Let's define a helper function in Neovim's config directory:

```lua
--[[ ~/.config/nvim/lua/config/lsp/custom.lua ]]

local mod = {}

---@param server_name string
---@param settings_patcher fun(settings: table): table
function mod.patch_lsp_settings(server_name, settings_patcher)
  local function patch_settings(client)
    client.config.settings = settings_patcher(client.config.settings)
    client.notify("workspace/didChangeConfiguration", {
      settings = client.config.settings,
    })
  end

  local clients = vim.lsp.get_active_clients({ name = server_name })
  if #clients > 0 then
    patch_settings(clients[1])
    return
  end

  vim.api.nvim_create_autocmd("LspAttach", {
    callback = function(args)
      local client = vim.lsp.get_client_by_id(args.data.client_id)
      if client.name == server_name then
        patch_settings(client)
        return true
      end
    end,
  })
end

return mod
```

Now let's say you want to setup Lua Language Server (a.k.a [`sumneko_lua`](https://github.com/sumneko/lua-language-server)) for [Hammerspoon](https://www.hammerspoon.org) config directory. For that, make sure:

- LSP is set up correctly in your Neovim config.
- [EmmyLua](https://www.hammerspoon.org/Spoons/EmmyLua.html) spoon is installed and loaded in your Hammerspoon config.

Next, create your project local config file in the Hammerspoon config directory (i.e. `~/.config/hammerspoon`):

```lua
--[[ ~/.config/hammerspoon/.nvimrc.lua ]]

require("config.lsp.custom").patch_lsp_settings("sumneko_lua", function(settings)
  settings.Lua.diagnostics.globals = { "hs", "spoon" }

  settings.Lua.workspace.library = {}

  local hammerspoon_emmpylua_annotations = vim.fn.expand("~/.config/hammerspoon/Spoons/EmmyLua.spoon/annotations")
  if vim.fn.isdirectory(hammerspoon_emmpylua_annotations) == 1 then
    table.insert(settings.Lua.workspace.library, hammerspoon_emmpylua_annotations)
  end

  return settings
end)
```

If you open Neovim in that directory now, `exrc.nvim` will run the `.nvimrc.lua` file to change the settings for `sumneko_lua` to work with Hammerspoon's APIs. 🤘🏼

That's it for today!

You can find all the snippets here [Gist: Neovim - Project Local Config with exrc.nvim](https://gist.github.com/MunifTanjim/7a34aa247cbabdec50f4de45fffdf53e).

And the plugin repository here: [MunifTanjim/exrc.nvim](https://github.com/MunifTanjim/exrc.nvim).

Have a great day! 😃

---

_Originally published at <a href="https://muniftanjim.dev/blog/neovim-project-local-config-with-exrc-nvim/" rel="canonical">muniftanjim.dev</a> on August 11, 2022._