DEV Community

Munif Tanjim
Munif Tanjim

Posted on • Originally published at muniftanjim.dev

Neovim - Project Local Config with exrc.nvim

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:

--[[ ~/.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
Enter fullscreen mode Exit fullscreen mode

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:

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

vim.o.exrc = true
Enter fullscreen mode Exit fullscreen mode

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.

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

vim.o.secure = true
Enter fullscreen mode Exit fullscreen mode

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

Plugin: 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:

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

vim.o.exrc = false

require("exrc").setup({
  files = {
    ".nvimrc.lua",
    ".nvimrc",
    ".exrc.lua",
    ".exrc",
  },
})
Enter fullscreen mode Exit fullscreen mode

Now you can add local config file for your project:

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

print("This is awesome!")
Enter fullscreen mode Exit fullscreen mode

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

exrc.nvim Prompt

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:

--[[ ~/.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
Enter fullscreen mode Exit fullscreen mode

Now let's say you want to setup Lua Language Server (a.k.a sumneko_lua) for Hammerspoon config directory. For that, make sure:

  • LSP is set up correctly in your Neovim config.
  • EmmyLua 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):

--[[ ~/.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)
Enter fullscreen mode Exit fullscreen mode

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.

And the plugin repository here: MunifTanjim/exrc.nvim.

Have a great day! 😃


Originally published at muniftanjim.dev on August 11, 2022.

Top comments (0)