3 " Author: Markus Braun <markus.braun@krawel.de>
4 " Summary: Vim plugin to search local vimrc files and load them.
5 " Licence: This program is free software: you can redistribute it and/or modify
6 " it under the terms of the GNU General Public License as published by
7 " the Free Software Foundation, either version 3 of the License, or
8 " (at your option) any later version.
10 " This program is distributed in the hope that it will be useful,
11 " but WITHOUT ANY WARRANTY; without even the implied warranty of
12 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 " GNU General Public License for more details.
15 " You should have received a copy of the GNU General Public License
16 " along with this program. If not, see <http://www.gnu.org/licenses/>.
18 " Section: Plugin header {{{1
20 " guard against multiple loads {{{2
21 if (exists("g:loaded_localvimrc") || &cp)
24 let g:loaded_localvimrc = 2
26 " check for correct vim version {{{2
31 " define default "localvimrc_name" {{{2
32 if (!exists("g:localvimrc_name"))
33 let s:localvimrc_name = ".lvimrc"
35 let s:localvimrc_name = g:localvimrc_name
38 " define default "localvimrc_count" {{{2
39 if (!exists("g:localvimrc_count"))
40 let s:localvimrc_count = -1
42 let s:localvimrc_count = g:localvimrc_count
45 " define default "localvimrc_sandbox" {{{2
46 " copy to script local variable to prevent .lvimrc disabling the sandbox
48 if (!exists("g:localvimrc_sandbox"))
49 let s:localvimrc_sandbox = 1
51 let s:localvimrc_sandbox = g:localvimrc_sandbox
54 " define default "localvimrc_ask" {{{2
55 " copy to script local variable to prevent .lvimrc disabling the sandbox
57 if (!exists("g:localvimrc_ask"))
58 let s:localvimrc_ask = 1
60 let s:localvimrc_ask = g:localvimrc_ask
63 " define default "localvimrc_whitelist" {{{2
64 " copy to script local variable to prevent .lvimrc modifying the whitelist.
65 if (!exists("g:localvimrc_whitelist"))
66 let s:localvimrc_whitelist = "^$" " This never matches a file
68 let s:localvimrc_whitelist = g:localvimrc_whitelist
71 " define default "localvimrc_blacklist" {{{2
72 " copy to script local variable to prevent .lvimrc modifying the blacklist.
73 if (!exists("g:localvimrc_blacklist"))
74 let s:localvimrc_blacklist = "^$" " This never matches a file
76 let s:localvimrc_blacklist = g:localvimrc_blacklist
79 " initialize answer dictionary {{{2
80 let s:localvimrc_answers = {}
82 " initialize checksum dictionary {{{2
83 let s:localvimrc_checksums = {}
85 " define default "localvimrc_persistent" {{{2
86 " make decisions persistent over multiple vim runs
87 if (!exists("g:localvimrc_persistent"))
88 let s:localvimrc_persistent = 0
90 let s:localvimrc_persistent = g:localvimrc_persistent
93 " define default "localvimrc_debug" {{{2
94 if (!exists("g:localvimrc_debug"))
95 let g:localvimrc_debug = 0
98 " Section: Autocmd setup {{{1
104 " call s:LocalVimRC() when creating ore reading any file
105 autocmd VimEnter,BufNewFile,BufRead * call s:LocalVimRC()
109 " Section: Functions {{{1
111 " Function: s:LocalVimRC() {{{2
113 " search all local vimrc files from current directory up to root directory and
114 " source them in reverse order.
116 function! s:LocalVimRC()
118 call s:LocalVimRCDebug(1, "==================================================")
121 call s:LocalVimRCDebug(1, "localvimrc.vim " . g:loaded_localvimrc)
123 " read persistent information
124 call s:LocalVimRCReadPersistent()
126 " only consider normal buffers (skip especially CommandT's GoToFile buffer)
128 call s:LocalVimRCDebug(1, "not a normal buffer, exiting")
132 " directory of current file (correctly escaped)
133 let l:directory = fnameescape(expand("%:p:h"))
134 if empty(l:directory)
135 let l:directory = fnameescape(getcwd())
137 call s:LocalVimRCDebug(2, "searching directory \"" . l:directory . "\"")
139 " generate a list of all local vimrc files with absolute file names along path to root
141 for l:rcfile in findfile(s:localvimrc_name, l:directory . ";", -1)
142 let l:absolute[resolve(fnamemodify(l:rcfile, ":p"))] = ""
144 let l:rcfiles = sort(keys(l:absolute))
145 call s:LocalVimRCDebug(1, "found files: " . string(l:rcfiles))
147 " shrink list of found files
148 if (s:localvimrc_count >= 0 && s:localvimrc_count < len(l:rcfiles))
149 call remove(l:rcfiles, 0, len(l:rcfiles) - s:localvimrc_count - 1)
151 call s:LocalVimRCDebug(1, "candidate files: " . string(l:rcfiles))
153 " source all found local vimrc files along path from root (reverse order)
155 for l:rcfile in l:rcfiles
156 call s:LocalVimRCDebug(2, "processing \"" . l:rcfile . "\"")
157 let l:rcfile_load = "unknown"
159 if filereadable(l:rcfile)
160 " check if whitelisted
161 if (l:rcfile_load == "unknown")
162 if (match(l:rcfile, s:localvimrc_whitelist) != -1)
163 call s:LocalVimRCDebug(2, l:rcfile . " is whitelisted")
164 let l:rcfile_load = "yes"
168 " check if blacklisted
169 if (l:rcfile_load == "unknown")
170 if (match(l:rcfile, s:localvimrc_blacklist) != -1)
171 call s:LocalVimRCDebug(2, l:rcfile . " is blacklisted")
172 let l:rcfile_load = "no"
176 " check if an answer has been given for the same file
177 if exists("s:localvimrc_answers[l:rcfile]")
178 if (s:LocalVimRCCheckChecksum(l:rcfile) == 1)
179 call s:LocalVimRCDebug(2, "reuse previous answer \"" . s:localvimrc_answers[l:rcfile] . "\"")
182 if (s:localvimrc_answers[l:rcfile] =~? '^y$')
183 let l:rcfile_load = "yes"
184 elseif (s:localvimrc_answers[l:rcfile] =~? '^n$')
185 let l:rcfile_load = "no"
188 call s:LocalVimRCDebug(2, "checksum mismatch, no answer reuse")
192 " ask if in interactive mode
193 if (l:rcfile_load == "unknown")
194 if (s:localvimrc_ask == 1)
195 if (l:answer !~? "^a$")
196 call s:LocalVimRCDebug(2, "need to ask")
198 while (l:answer !~? '^[ynaq]$')
199 if (s:localvimrc_persistent == 0)
200 let l:message = "localvimrc: source " . l:rcfile . "? ([y]es/[n]o/[a]ll/[q]uit) "
201 elseif (s:localvimrc_persistent == 1)
202 let l:message = "localvimrc: source " . l:rcfile . "? ([y]es/[n]o/[a]ll/[q]uit ; persistent [Y]es/[N]o/[A]ll) "
204 let l:message = "localvimrc: source " . l:rcfile . "? ([y]es/[n]o/[a]ll/[q]uit) "
206 let l:answer = input(l:message)
207 call s:LocalVimRCDebug(2, "answer is \"" . l:answer . "\"")
211 " make answer upper case if persistence is 2 ("force")
212 if (s:localvimrc_persistent == 2)
213 let l:answer = toupper(l:answer)
217 if (l:answer =~? "^y$")
218 let s:localvimrc_answers[l:rcfile] = l:answer
219 elseif (l:answer =~? "^n$")
220 let s:localvimrc_answers[l:rcfile] = l:answer
221 elseif (l:answer =~# "^a$")
222 let s:localvimrc_answers[l:rcfile] = "y"
223 elseif (l:answer =~# "^A$")
224 let s:localvimrc_answers[l:rcfile] = "Y"
228 if (l:answer =~? '^[ya]$')
229 let l:rcfile_load = "yes"
230 elseif (l:answer =~? "^q$")
231 call s:LocalVimRCDebug(1, "ended processing files")
237 " load unconditionally if in non-interactive mode
238 if (l:rcfile_load == "unknown")
239 if (s:localvimrc_ask == 0)
240 let l:rcfile_load = "yes"
244 " should this rc file be loaded?
245 if (l:rcfile_load == "yes")
246 let l:command = "silent "
248 " add 'sandbox' if requested
249 if (s:localvimrc_sandbox != 0)
250 let l:command .= "sandbox "
251 call s:LocalVimRCDebug(2, "using sandbox")
253 let l:command .= "source " . fnameescape(l:rcfile)
255 " execute the command
257 call s:LocalVimRCDebug(1, "sourced " . l:rcfile)
260 call s:LocalVimRCDebug(1, "skipping " . l:rcfile)
263 " calculate checksum for each processed file
264 call s:LocalVimRCCalcChecksum(l:rcfile)
272 " make information persistent
273 call s:LocalVimRCWritePersistent()
276 call s:LocalVimRCDebug(1, "==================================================")
279 " Function: s:LocalVimRCCalcChecksum(filename) {{{2
281 " calculate checksum and store it in dictionary
283 function! s:LocalVimRCCalcChecksum(filename)
284 let l:file = fnameescape(a:filename)
285 let l:checksum = getfsize(l:file) . getfperm(l:file) . getftime(l:file)
286 let s:localvimrc_checksums[l:file] = l:checksum
288 call s:LocalVimRCDebug(3, "checksum calc -> ".l:file . " : " . l:checksum)
291 " Function: s:LocalVimRCCheckChecksum(filename) {{{2
293 " Check checksum in dictionary. Return "0" if it does not exist, "1" otherwise
295 function! s:LocalVimRCCheckChecksum(filename)
297 let l:file = fnameescape(a:filename)
298 let l:checksum = getfsize(l:file) . getfperm(l:file) . getftime(l:file)
299 " overwrite answers with persistent data
300 if exists("s:localvimrc_checksums[l:file]")
301 call s:LocalVimRCDebug(3, "checksum check -> ".l:file . " : " . l:checksum . " : " . s:localvimrc_checksums[l:file])
303 if (s:localvimrc_checksums[l:file] == l:checksum)
312 " Function: s:LocalVimRCReadPersistent() {{{2
314 " read decision variables from global variable
316 function! s:LocalVimRCReadPersistent()
317 if (s:localvimrc_persistent == 1)
318 if stridx(&viminfo, "!") >= 0
319 if exists("g:LOCALVIMRC_ANSWERS")
320 for l:rcfile in keys(g:LOCALVIMRC_ANSWERS)
321 " overwrite answers with persistent data
322 let s:localvimrc_answers[l:rcfile] = g:LOCALVIMRC_ANSWERS[l:rcfile]
324 call s:LocalVimRCDebug(3, "read answer persistent data: " . string(s:localvimrc_answers))
326 if exists("g:LOCALVIMRC_CHECKSUMS")
327 " overwrite checksums with persistent data
328 let s:localvimrc_checksums = g:LOCALVIMRC_CHECKSUMS
329 call s:LocalVimRCDebug(3, "read checksum persistent data: " . string(s:localvimrc_checksums))
335 " Function: s:LocalVimRCWritePersistent() {{{2
337 " write decision variables to global variable to make them persistent
339 function! s:LocalVimRCWritePersistent()
340 if (s:localvimrc_persistent == 1)
341 " select only data relevant for persistence
342 let l:persistent_answers = filter(copy(s:localvimrc_answers), 'v:val =~# "^[YN]$"')
343 let l:persistent_checksums = {}
344 for l:rcfile in keys(l:persistent_answers)
345 let l:persistent_checksums[l:rcfile] = s:localvimrc_checksums[l:rcfile]
348 " if there are answers to store and global variables are enabled for viminfo
349 if (len(l:persistent_answers) > 0)
350 if (stridx(&viminfo, "!") >= 0)
351 let g:LOCALVIMRC_ANSWERS = l:persistent_answers
352 call s:LocalVimRCDebug(3, "write answer persistent data: " . string(g:LOCALVIMRC_ANSWERS))
353 let g:LOCALVIMRC_CHECKSUMS = l:persistent_checksums
354 call s:LocalVimRCDebug(3, "write checksum persistent data: " . string(g:LOCALVIMRC_CHECKSUMS))
356 call s:LocalVimRCDebug(3, "viminfo setting has no '!' flag, no persistence available")
357 call s:LocalVimRCError("viminfo setting has no '!' flag, no persistence available")
361 if exists("g:LOCALVIMRC_ANSWERS")
362 unlet g:LOCALVIMRC_ANSWERS
363 call s:LocalVimRCDebug(3, "deleted answer persistent data")
365 if exists("g:LOCALVIMRC_CHECKSUMS")
366 unlet g:LOCALVIMRC_CHECKSUMS
367 call s:LocalVimRCDebug(3, "deleted checksum persistent data")
373 " Function: s:LocalVimRCClear() {{{2
375 " clear all stored data
377 function! s:LocalVimRCClear()
378 if exists("s:localvimrc_answers")
379 unlet s:localvimrc_answers
380 call s:LocalVimRCDebug(3, "deleted answer local data")
382 if exists("s:localvimrc_checksums")
383 unlet s:localvimrc_checksums
384 call s:LocalVimRCDebug(3, "deleted checksum local data")
386 if exists("g:LOCALVIMRC_ANSWERS")
387 unlet g:LOCALVIMRC_ANSWERS
388 call s:LocalVimRCDebug(3, "deleted answer persistent data")
390 if exists("g:LOCALVIMRC_CHECKSUMS")
391 unlet g:LOCALVIMRC_CHECKSUMS
392 call s:LocalVimRCDebug(3, "deleted checksum persistent data")
396 " Function: s:LocalVimRCError(text) {{{2
398 " output error message
400 function! s:LocalVimRCError(text)
401 echohl ErrorMsg | echo "localvimrc: " . a:text | echohl None
404 " Function: s:LocalVimRCDebug(level, text) {{{2
406 " output debug message, if this message has high enough importance
408 function! s:LocalVimRCDebug(level, text)
409 if (g:localvimrc_debug >= a:level)
410 echom "localvimrc: " . a:text
414 " Section: Commands {{{1
415 command! LocalVimRCClear call s:LocalVimRCClear()
417 " vim600: foldmethod=marker foldlevel=0 :