DEV Community

Heiker
Heiker

Posted on • Updated on

Project based config in vim

Every once in a while I find myself wanting to have a specific configuration in a project. Is not that I needed it, but it would be nice to have. There are ways to achieve this, some "better" than others. Today I'm going to share with you a couple ways we can do it and my current solution for this.

editorconfig

If all you want is to keep the aesthetic of your code consistent, this is the way to go.

With this plugin vim can read the popular .editorconfig format, which allows you to set options like the indent style, tab size, maximum length of line... that kind of stuff.

The limited set of options offered by this plugin make it the safest choice. Because arbitrary code execution is cool until is not.

local vimrc

Speaking of arbitrary code execution... what if we want that? That plugin is one option.

This one will scan your project searching for a .vimrc file and source it. Now you have unlimited power, in your "local" .vimrc you can have anything you want. Sounds good, right? Also kinda dangerous, that's why this plugin also offers a "hash protection." Basically, it checks the integrity of the file before executing it. If it turns out it changed since the last time you used it, it will prompt you and ask you if you're sure you want to source the .vimrc.

For me, it still feels wrong. Maybe the hash protection is enough, maybe it isn't. It just feels wrong.

nvim project

This one is similar to local vimrc in that it will look for a vim file or lua file (is a neovim plugin) and execute it. The difference is the "local config" is not meant to live in your project folder, it will live on the same place your init.vim is (it will create a "projects" folder). What it will do is check if your current project has a config file in the folder this plugin manages, and of course it will execute it if it does exists.

Cool thing about it is that you don't have to worry about some sneaky .vimrc file lurking around looking to cause you harm. On the other hand, this plugin doesn't check the integrity of the files it executes. It shouldn't be such a big deal because you won't be downloading random files in the "projects" folder, you should know for sure what's in there.

My problem with this plugin is that is written in lua, and it's somewhat opinionated in the way it handles your local config.

Do It Yourself

The current solution I'm exploring is very much inspired by nvim-project. Is like a "low tech" version of that. The goal is to have more control over the config files and at same time keep it simple.

Ultimately want I want to do is map a project folder to a config file. That's pretty much it. vim can do that.

I did some experiments and it turns out this works.

let s:project_config = {
  \ "/tmp/test": "IT WORKS"
  \ }

echo s:project_config[getcwd()]
Enter fullscreen mode Exit fullscreen mode

If I open vim in /tmp/test folder and run :source /path/to/test.vim it will show me the message "IT WORKS." That is half of what I need. Let's look for the other half.

 let s:project_config = {
-  \ "/tmp/test": "IT WORKS"
+  \ "/tmp/test": "/tmp/other/localrc.vim"
   \ }
-
- echo s:project_config[getcwd()]
+ echo get(s:project_config, getcwd(), '')
Enter fullscreen mode Exit fullscreen mode

Now /tmp/test will point to another file in some random folder. And instead of accessing the property on project_config directly we use the get function which will provide a nice default value if we don't get what we want.

The next step is to check the local config exists and can be read.

let s:project_config = {
  \ "/tmp/test": "/tmp/other/localrc.vim"
  \ }

let s:file = get(s:project_config, getcwd(), '')

if filereadable(s:file)
  execute 'source' s:file
endif
Enter fullscreen mode Exit fullscreen mode

That is the core feature I want. Now lets make it nicer.

I want to be able to go to the "local config" from my project folder. Additionaly, I want to be able to create a local config if there isn't one.

  if filereadable(s:file)
    execute 'source' s:file
+   execute 'nnoremap <C-x>c :edit' s:file '<cr>'
  else
+   execute 'nnoremap <C-x>c :edit /location/of/projects-rc/'
  endif
Enter fullscreen mode Exit fullscreen mode

I know <C-x>c is a horrible shortcut. Look, I'm running out combination on my vimrc.

So, if the local config exists <C-x>c will take me to that file, if not it will prompt me to create one with the :edit command.

What happens when I want to add the file to s:project_config? I can solve that, by adding this on the last line.

execute 'nnoremap <C-x>C :edit' expand('<sfile>:p') '<cr>'
Enter fullscreen mode Exit fullscreen mode

There you go. All set.

The last thing I want to take care of is avoid writing long paths. So... have you ever heard about currying? Not the food. Doesn't matter, lets add a litte helper function.

function! s:prefix(path) abort
  return {file -> expand(a:path . file)}
endfunction
Enter fullscreen mode Exit fullscreen mode

This is a function that returns another function. Why? Because I can. Anyway, what we are doing here is creating a "path" that the expand function will use. We need expand if want to use special characters like ~, or make use of some special tokens vim provides (like % or :p). Now you could do something like this.

let s:my_code = s:prefix('~/Projects/')
let s:work_code = s:prefix('~/some/path/to/your/work/folder/')
let s:localrc = s:prefix('~/.vim/project-rc/')

let s:project_config = {
  \ s:my_code('cool-site'): s:localrc('cool-site.vim'),
  \ s:work_code('legacy-site'): s:localrc('legacy-site.vim')
  \ }

let s:file = get(s:project_config, getcwd(), '')

if filereadable(s:file)
  execute 'source' s:file
  execute 'nnoremap <C-x>c :edit' s:file '<cr>'
else
  execute 'nnoremap <C-x>c :edit' s:localrc('')
endif

execute 'nnoremap <C-x>C :edit' expand('<sfile>:p') '<cr>'
Enter fullscreen mode Exit fullscreen mode

And this will work just fine.

What about the integrity of files? I haven't dealt with that just yet. But I figure you could manage ~/.vim/project-rc/ with git. Next, find a way to do a git status -s <file> before executing the file, so if git status gives you something then don't run the file (it should be clean). And just commit every change when you modify anything in ~/.vim/projects-rc.

That's it for today. Let me know what you think about this.

Update (august 2021):

This solution of mine, I recommend using it as an "after plugin". Basically, save the script in this path ~/.vim/after/plugin/localrc.vim.

That should make vim source that script automatically after your "regular" plugins.

Now for the neovim fans out there, if for some reason you would want this but written in lua, here it is:

local fn = vim.fn
local map = function(lhs, rhs)
  vim.api.nvim_set_keymap('n', lhs, rhs, {noremap = true})
end

local prefix = function(path)
  return function(file) return fn.expand(path .. file) end
end

local my_code = prefix('~/Projects/')
local work_code = prefix('~/some/path/to/your/work/folder/')
local localrc = prefix('~/.config/nvim/project-rc/')

local project_config = {
  [my_code('cool-site')] = localrc('cool-site.lua'),
  [work_code('legacy-site')] = localrc('legacy-site.lua')
}

local file = project_config[fn.getcwd()] or ''

if fn.filereadable(file) == 1 then
  dofile(file)
  map('<C-x>c', ':edit ' .. file)
else
  map('<C-x>c', ':edit ' .. localrc(''))
end

map('<C-x>C', ':edit ' .. fn.expand('<sfile>:p'))
Enter fullscreen mode Exit fullscreen mode

Save this in ~/.config/nvim/after/plugin/localrc.lua and it should work like a charm.


If you find this article useful and want to support my efforts, buy me a coffee ☕.

buy me a coffee

Top comments (2)

Collapse
 
windwp profile image
windwp

I am the author of that plugin nvim-projectconfig. I know my plugin doesn't has that feature to customize a path of config file. But it is enough to me. I don't need to add a project path to my init.lua

It also has a feature to create and edit config file similar to your vim code and I map it to ep.
btw good article and i hope you have more post like that

Collapse
 
smitray profile image
Smit Ray

One request, kindly make the plugin to find the config from project folder