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

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

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

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 = falserequire("exrc").setup({
files = {
".nvimrc.lua",
".nvimrc",
".exrc.lua",
".exrc",
},
})

Now you can add local config file for your project:

--[[ /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

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

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)

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 https://muniftanjim.dev on August 11, 2022.

--

--

Full Stack Software Engineer with years of professional experience with JavaScript ecosystem. On my free time, I maintain a few open-source libraries and tools.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Munif Tanjim

Munif Tanjim

Full Stack Software Engineer with years of professional experience with JavaScript ecosystem. On my free time, I maintain a few open-source libraries and tools.