From 1b63ed43efe6c575185df8e27559e70f2979964b Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Mon, 22 Apr 2013 10:04:21 +0100 Subject: [PATCH] Added localvimrc plugin. Parse any .lvimrc files in the current or parent directories when starting vim. --- .vim/doc/localvimrc.txt | 191 +++++++++++++++++++++ .vim/plugin/localvimrc.vim | 417 +++++++++++++++++++++++++++++++++++++++++++++ .vimrc | 6 + 3 files changed, 614 insertions(+) create mode 100644 .vim/doc/localvimrc.txt create mode 100644 .vim/plugin/localvimrc.vim diff --git a/.vim/doc/localvimrc.txt b/.vim/doc/localvimrc.txt new file mode 100644 index 0000000..2ec9c8d --- /dev/null +++ b/.vim/doc/localvimrc.txt @@ -0,0 +1,191 @@ +*localvimrc.txt* For Vim version 7.3 Last change: 2012 September 25 + +localvimrc Version 2.1.0 *localvimrc* *lvimrc* + +|localvimrc-description| Description +|localvimrc-commands| Commands +|localvimrc-settings| Settings +|localvimrc-contribute| Contribute +|localvimrc-credits| Credits +|localvimrc-changelog| Changelog + +============================================================================== +DESCRIPTION *localvimrc-description* + +This plugin searches for local vimrc files in the file system tree of the +currently opened file. By default it searches for all ".lvimrc" files from the +file's directory up to the root directory and loads them in reverse order. The +filename and amount of loaded files are customizable through global variables. + +For security reasons it the plugin asks for confirmation before loading a +local vimrc file and loads it using |:sandbox| command. The plugin asks once +per session and local vimrc before loading it, if the file didn't change since +previous loading. + +It is possible to define a whitelist and a blacklist of local vimrc files that +are loaded or ignored unconditionally. + + +============================================================================== +COMMANDS *localvimrc-commands* + +------------------------------------------------------------------------------ +*LocalVimRCClear* + +Clear all stored decisions made in the past, when the plugin asked about +sourcing a local vimrc file. + +============================================================================== +SETTINGS *localvimrc-settings* + +Use: > + let g:option_name=option_value + +to set them in your global vimrc. + +------------------------------------------------------------------------------ +*g:localvimrc_name* + +Filename of local vimrc files. + +Default: ".lvimrc" + +------------------------------------------------------------------------------ +*g:localvimrc_count* + +On the way from root, the last localvimrc_count files are sourced. + +Default: -1 (all) + +------------------------------------------------------------------------------ +*g:localvimrc_sandbox* + +Source the found local vimrc files in a sandbox for security reasons. + +Value Description~ +0 Don't load vimrc file in a sandbox. +1 Load vimrc file in a sandbox. + +Default: 1 + +------------------------------------------------------------------------------ +*g:localvimrc_ask* + +Ask before sourcing any local vimrc file. In a vim session the question is +only asked once as long as the local vimrc file has not been changed. + +Value Description~ +0 Don't ask before loading a vimrc file. +1 Ask before loading a vimrc file. + +Default: 1 + +------------------------------------------------------------------------------ +*g:localvimrc_persistent* + +Make the decisions given when asked before sourcing local vimrc files +persistent over multiple vim runs. This is done by storing the decisions in +viminfo. Therefore it is required to include the |viminfo-!| flag in your +viminfo setting. + +Value Description~ +0 Don't store and restore any decisions. +1 Store and restore decisions only if the answer was given in upper + case (Y/N/A). +2 Store and restore all decisions. + +Default: 0 + +------------------------------------------------------------------------------ +*g:localvimrc_whitelist* + +If a local vimrc file matches the regular expression given by +|g:localvimrc_whitelist| this file is loaded unconditionally. + +Files matching |g:localvimrc_whitelist| are sourced even if they are matched +by |g:localvimrc_blacklist|. + +See |regular-expression| for patterns that are accepted. + +Example: + +" whitelist all local vimrc files in users project foo and bar +let g:localvimrc_whitelist='/home/user/projects/\(foo\|bar\)/.*' + +Default: No whitelist + +------------------------------------------------------------------------------ +*g:localvimrc_blacklist* + +If a local vimrc file matches the regular expression given by +|g:localvimrc_blacklist| this file is skipped unconditionally. + +Files matching |g:localvimrc_whitelist| are sourced even if they are matched +by |g:localvimrc_blacklist|. + +See |regular-expression| for patterns that are accepted. + +Example: + +" blacklist all local vimrc files in shared project directory +let g:localvimrc_whitelist='/share/projects/.*' + +Default: No blacklist + +------------------------------------------------------------------------------ +*g:localvimrc_debug* + +Debug level for this script. + +Default: 0 + +============================================================================== +CONTRIBUTE *localvimrc-contribute* + +To contact the author (Markus Braun), please email: markus.braun@krawel.de + +If you think this plugin could be improved, fork on GitHub and send a pull +request or just tell me your ideas. + +GitHub: https://github.com/embear/vim-localvimrc + +============================================================================== +CREDITS *localvimrc-credits* + +- Simon Howard for his hint about "sandbox" +- Mark Weber for the idea of using checksums +- Daniel Hahler for various patches + +============================================================================== +CHANGELOG *localvimrc-changelog* + +v2.1.0 : 2012-09-25 + - add possibility to make decisions persistent + - use full file path when storing decisions + +v2.0.0 : 2012-04-05 + - added g:localvimrc_whitelist and g:localvimrc_blacklist settings. + - ask only once per session and local vimrc before loading it, if it didn't + change. + +v2758 : 2009-05-11 + - source .lvimrc in a sandbox to better maintain security, configurable + using g:localvimrc_sandbox. + - ask user before sourcing any local vimrc file, configurable using + g:localvimrc_ask. + +v1870 : 2007-09-28 + - new configuration variable g:localvimrc_name to change filename. + - new configuration variable g:localvimrc_count to limit number of loaded + files. + +v1613 : 2007-04-05 + - switched to arrays in vim 7. + - escape file/path names correctly. + +v1.2 : 2002-10-09 + - initial version + +============================================================================== + +vim:tw=78:ts=8:ft=help:norl: diff --git a/.vim/plugin/localvimrc.vim b/.vim/plugin/localvimrc.vim new file mode 100644 index 0000000..5cd11e6 --- /dev/null +++ b/.vim/plugin/localvimrc.vim @@ -0,0 +1,417 @@ +" 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 : diff --git a/.vimrc b/.vimrc index 28d060e..1886971 100644 --- a/.vimrc +++ b/.vimrc @@ -621,6 +621,9 @@ version 5.4 " Reuse windows when using sbuffer. se switchbuf=useopen +" Allow persistent variable saving for localvimrc. +se viminfo+=! + " Do we have Unicode? fun! Has_Unicode() "{{{2 if ! has('multi_byte') @@ -1257,6 +1260,9 @@ let g:bufExplorerSplitOutPathName=0 let g:NERDSpaceDelims=1 endif "}}}1 +" localvimrc. +let g:localvimrc_persistent=1 + """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Handle options only available in Vim 7.2 and above. """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -- 2.7.4