" Name: localvimrc.vim " Version: 2.1.0 " Author: Markus Braun " Summary: Vim plugin to search local vimrc files and load them. " Licence: This program is free software: you can redistribute it and/or modify " it under the terms of the GNU General Public License as published by " the Free Software Foundation, either version 3 of the License, or " (at your option) any later version. " " This program is distributed in the hope that it will be useful, " but WITHOUT ANY WARRANTY; without even the implied warranty of " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the " GNU General Public License for more details. " " You should have received a copy of the GNU General Public License " along with this program. If not, see . " " Section: Plugin header {{{1 " guard against multiple loads {{{2 if (exists("g:loaded_localvimrc") || &cp) finish endif let g:loaded_localvimrc = 2 " check for correct vim version {{{2 if version < 700 finish endif " define default "localvimrc_name" {{{2 if (!exists("g:localvimrc_name")) let s:localvimrc_name = ".lvimrc" else let s:localvimrc_name = g:localvimrc_name endif " define default "localvimrc_count" {{{2 if (!exists("g:localvimrc_count")) let s:localvimrc_count = -1 else let s:localvimrc_count = g:localvimrc_count endif " define default "localvimrc_sandbox" {{{2 " copy to script local variable to prevent .lvimrc disabling the sandbox " again. if (!exists("g:localvimrc_sandbox")) let s:localvimrc_sandbox = 1 else let s:localvimrc_sandbox = g:localvimrc_sandbox endif " define default "localvimrc_ask" {{{2 " copy to script local variable to prevent .lvimrc disabling the sandbox " again. if (!exists("g:localvimrc_ask")) let s:localvimrc_ask = 1 else let s:localvimrc_ask = g:localvimrc_ask endif " define default "localvimrc_whitelist" {{{2 " copy to script local variable to prevent .lvimrc modifying the whitelist. if (!exists("g:localvimrc_whitelist")) let s:localvimrc_whitelist = "^$" " This never matches a file else let s:localvimrc_whitelist = g:localvimrc_whitelist endif " define default "localvimrc_blacklist" {{{2 " copy to script local variable to prevent .lvimrc modifying the blacklist. if (!exists("g:localvimrc_blacklist")) let s:localvimrc_blacklist = "^$" " This never matches a file else let s:localvimrc_blacklist = g:localvimrc_blacklist endif " initialize answer dictionary {{{2 let s:localvimrc_answers = {} " initialize checksum dictionary {{{2 let s:localvimrc_checksums = {} " define default "localvimrc_persistent" {{{2 " make decisions persistent over multiple vim runs if (!exists("g:localvimrc_persistent")) let s:localvimrc_persistent = 0 else let s:localvimrc_persistent = g:localvimrc_persistent endif " define default "localvimrc_debug" {{{2 if (!exists("g:localvimrc_debug")) let g:localvimrc_debug = 0 endif " Section: Autocmd setup {{{1 if has("autocmd") augroup localvimrc autocmd! " call s:LocalVimRC() when creating ore reading any file autocmd VimEnter,BufNewFile,BufRead * call s:LocalVimRC() augroup END endif " Section: Functions {{{1 " Function: s:LocalVimRC() {{{2 " " search all local vimrc files from current directory up to root directory and " source them in reverse order. " function! s:LocalVimRC() " begin marker call s:LocalVimRCDebug(1, "==================================================") " print version call s:LocalVimRCDebug(1, "localvimrc.vim " . g:loaded_localvimrc) " read persistent information call s:LocalVimRCReadPersistent() " only consider normal buffers (skip especially CommandT's GoToFile buffer) if (&buftype != "") call s:LocalVimRCDebug(1, "not a normal buffer, exiting") return endif " directory of current file (correctly escaped) let l:directory = fnameescape(expand("%:p:h")) if empty(l:directory) let l:directory = fnameescape(getcwd()) endif call s:LocalVimRCDebug(2, "searching directory \"" . l:directory . "\"") " generate a list of all local vimrc files with absolute file names along path to root let l:absolute = {} for l:rcfile in findfile(s:localvimrc_name, l:directory . ";", -1) let l:absolute[resolve(fnamemodify(l:rcfile, ":p"))] = "" endfor let l:rcfiles = sort(keys(l:absolute)) call s:LocalVimRCDebug(1, "found files: " . string(l:rcfiles)) " shrink list of found files if (s:localvimrc_count >= 0 && s:localvimrc_count < len(l:rcfiles)) call remove(l:rcfiles, 0, len(l:rcfiles) - s:localvimrc_count - 1) endif call s:LocalVimRCDebug(1, "candidate files: " . string(l:rcfiles)) " source all found local vimrc files along path from root (reverse order) let l:answer = "" for l:rcfile in l:rcfiles call s:LocalVimRCDebug(2, "processing \"" . l:rcfile . "\"") let l:rcfile_load = "unknown" if filereadable(l:rcfile) " check if whitelisted if (l:rcfile_load == "unknown") if (match(l:rcfile, s:localvimrc_whitelist) != -1) call s:LocalVimRCDebug(2, l:rcfile . " is whitelisted") let l:rcfile_load = "yes" endif endif " check if blacklisted if (l:rcfile_load == "unknown") if (match(l:rcfile, s:localvimrc_blacklist) != -1) call s:LocalVimRCDebug(2, l:rcfile . " is blacklisted") let l:rcfile_load = "no" endif endif " check if an answer has been given for the same file if exists("s:localvimrc_answers[l:rcfile]") if (s:LocalVimRCCheckChecksum(l:rcfile) == 1) call s:LocalVimRCDebug(2, "reuse previous answer \"" . s:localvimrc_answers[l:rcfile] . "\"") " check the answer if (s:localvimrc_answers[l:rcfile] =~? '^y$') let l:rcfile_load = "yes" elseif (s:localvimrc_answers[l:rcfile] =~? '^n$') let l:rcfile_load = "no" endif else call s:LocalVimRCDebug(2, "checksum mismatch, no answer reuse") endif endif " ask if in interactive mode if (l:rcfile_load == "unknown") if (s:localvimrc_ask == 1) if (l:answer !~? "^a$") call s:LocalVimRCDebug(2, "need to ask") let l:answer = "" while (l:answer !~? '^[ynaq]$') if (s:localvimrc_persistent == 0) let l:message = "localvimrc: source " . l:rcfile . "? ([y]es/[n]o/[a]ll/[q]uit) " elseif (s:localvimrc_persistent == 1) let l:message = "localvimrc: source " . l:rcfile . "? ([y]es/[n]o/[a]ll/[q]uit ; persistent [Y]es/[N]o/[A]ll) " else let l:message = "localvimrc: source " . l:rcfile . "? ([y]es/[n]o/[a]ll/[q]uit) " endif let l:answer = input(l:message) call s:LocalVimRCDebug(2, "answer is \"" . l:answer . "\"") endwhile endif " make answer upper case if persistence is 2 ("force") if (s:localvimrc_persistent == 2) let l:answer = toupper(l:answer) endif " store y/n answers if (l:answer =~? "^y$") let s:localvimrc_answers[l:rcfile] = l:answer elseif (l:answer =~? "^n$") let s:localvimrc_answers[l:rcfile] = l:answer elseif (l:answer =~# "^a$") let s:localvimrc_answers[l:rcfile] = "y" elseif (l:answer =~# "^A$") let s:localvimrc_answers[l:rcfile] = "Y" endif " check the answer if (l:answer =~? '^[ya]$') let l:rcfile_load = "yes" elseif (l:answer =~? "^q$") call s:LocalVimRCDebug(1, "ended processing files") break endif endif endif " load unconditionally if in non-interactive mode if (l:rcfile_load == "unknown") if (s:localvimrc_ask == 0) let l:rcfile_load = "yes" endif endif " should this rc file be loaded? if (l:rcfile_load == "yes") let l:command = "silent " " add 'sandbox' if requested if (s:localvimrc_sandbox != 0) let l:command .= "sandbox " call s:LocalVimRCDebug(2, "using sandbox") endif let l:command .= "source " . fnameescape(l:rcfile) " execute the command exec l:command call s:LocalVimRCDebug(1, "sourced " . l:rcfile) else call s:LocalVimRCDebug(1, "skipping " . l:rcfile) endif " calculate checksum for each processed file call s:LocalVimRCCalcChecksum(l:rcfile) endif endfor " clear command line redraw! " make information persistent call s:LocalVimRCWritePersistent() " end marker call s:LocalVimRCDebug(1, "==================================================") endfunction " Function: s:LocalVimRCCalcChecksum(filename) {{{2 " " calculate checksum and store it in dictionary " function! s:LocalVimRCCalcChecksum(filename) let l:file = fnameescape(a:filename) let l:checksum = getfsize(l:file) . getfperm(l:file) . getftime(l:file) let s:localvimrc_checksums[l:file] = l:checksum call s:LocalVimRCDebug(3, "checksum calc -> ".l:file . " : " . l:checksum) endfunction " Function: s:LocalVimRCCheckChecksum(filename) {{{2 " " Check checksum in dictionary. Return "0" if it does not exist, "1" otherwise " function! s:LocalVimRCCheckChecksum(filename) let l:return = 0 let l:file = fnameescape(a:filename) let l:checksum = getfsize(l:file) . getfperm(l:file) . getftime(l:file) " overwrite answers with persistent data if exists("s:localvimrc_checksums[l:file]") call s:LocalVimRCDebug(3, "checksum check -> ".l:file . " : " . l:checksum . " : " . s:localvimrc_checksums[l:file]) if (s:localvimrc_checksums[l:file] == l:checksum) let l:return = 1 endif endif return l:return endfunction " Function: s:LocalVimRCReadPersistent() {{{2 " " read decision variables from global variable " function! s:LocalVimRCReadPersistent() if (s:localvimrc_persistent == 1) if stridx(&viminfo, "!") >= 0 if exists("g:LOCALVIMRC_ANSWERS") for l:rcfile in keys(g:LOCALVIMRC_ANSWERS) " overwrite answers with persistent data let s:localvimrc_answers[l:rcfile] = g:LOCALVIMRC_ANSWERS[l:rcfile] endfor call s:LocalVimRCDebug(3, "read answer persistent data: " . string(s:localvimrc_answers)) endif if exists("g:LOCALVIMRC_CHECKSUMS") " overwrite checksums with persistent data let s:localvimrc_checksums = g:LOCALVIMRC_CHECKSUMS call s:LocalVimRCDebug(3, "read checksum persistent data: " . string(s:localvimrc_checksums)) endif endif endif endfunction " Function: s:LocalVimRCWritePersistent() {{{2 " " write decision variables to global variable to make them persistent " function! s:LocalVimRCWritePersistent() if (s:localvimrc_persistent == 1) " select only data relevant for persistence let l:persistent_answers = filter(copy(s:localvimrc_answers), 'v:val =~# "^[YN]$"') let l:persistent_checksums = {} for l:rcfile in keys(l:persistent_answers) let l:persistent_checksums[l:rcfile] = s:localvimrc_checksums[l:rcfile] endfor " if there are answers to store and global variables are enabled for viminfo if (len(l:persistent_answers) > 0) if (stridx(&viminfo, "!") >= 0) let g:LOCALVIMRC_ANSWERS = l:persistent_answers call s:LocalVimRCDebug(3, "write answer persistent data: " . string(g:LOCALVIMRC_ANSWERS)) let g:LOCALVIMRC_CHECKSUMS = l:persistent_checksums call s:LocalVimRCDebug(3, "write checksum persistent data: " . string(g:LOCALVIMRC_CHECKSUMS)) else call s:LocalVimRCDebug(3, "viminfo setting has no '!' flag, no persistence available") call s:LocalVimRCError("viminfo setting has no '!' flag, no persistence available") endif endif else if exists("g:LOCALVIMRC_ANSWERS") unlet g:LOCALVIMRC_ANSWERS call s:LocalVimRCDebug(3, "deleted answer persistent data") endif if exists("g:LOCALVIMRC_CHECKSUMS") unlet g:LOCALVIMRC_CHECKSUMS call s:LocalVimRCDebug(3, "deleted checksum persistent data") endif endif endfunction " Function: s:LocalVimRCClear() {{{2 " " clear all stored data " function! s:LocalVimRCClear() if exists("s:localvimrc_answers") unlet s:localvimrc_answers call s:LocalVimRCDebug(3, "deleted answer local data") endif if exists("s:localvimrc_checksums") unlet s:localvimrc_checksums call s:LocalVimRCDebug(3, "deleted checksum local data") endif if exists("g:LOCALVIMRC_ANSWERS") unlet g:LOCALVIMRC_ANSWERS call s:LocalVimRCDebug(3, "deleted answer persistent data") endif if exists("g:LOCALVIMRC_CHECKSUMS") unlet g:LOCALVIMRC_CHECKSUMS call s:LocalVimRCDebug(3, "deleted checksum persistent data") endif endfunction " Function: s:LocalVimRCError(text) {{{2 " " output error message " function! s:LocalVimRCError(text) echohl ErrorMsg | echo "localvimrc: " . a:text | echohl None endfunction " Function: s:LocalVimRCDebug(level, text) {{{2 " " output debug message, if this message has high enough importance " function! s:LocalVimRCDebug(level, text) if (g:localvimrc_debug >= a:level) echom "localvimrc: " . a:text endif endfunction " Section: Commands {{{1 command! LocalVimRCClear call s:LocalVimRCClear() " vim600: foldmethod=marker foldlevel=0 :