Clipboard plugin.
[profile.git] / .vim / plugin / localvimrc.vim
1 " Name:    localvimrc.vim
2 " Version: 2.1.0
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.
9 "
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.
14 "
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/>.
17 "
18 " Section: Plugin header {{{1
19
20 " guard against multiple loads {{{2
21 if (exists("g:loaded_localvimrc") || &cp)
22   finish
23 endif
24 let g:loaded_localvimrc = 2
25
26 " check for correct vim version {{{2
27 if version < 700
28   finish
29 endif
30
31 " define default "localvimrc_name" {{{2
32 if (!exists("g:localvimrc_name"))
33   let s:localvimrc_name = ".lvimrc"
34 else
35   let s:localvimrc_name = g:localvimrc_name
36 endif
37
38 " define default "localvimrc_count" {{{2
39 if (!exists("g:localvimrc_count"))
40   let s:localvimrc_count = -1
41 else
42   let s:localvimrc_count = g:localvimrc_count
43 endif
44
45 " define default "localvimrc_sandbox" {{{2
46 " copy to script local variable to prevent .lvimrc disabling the sandbox
47 " again.
48 if (!exists("g:localvimrc_sandbox"))
49   let s:localvimrc_sandbox = 1
50 else
51   let s:localvimrc_sandbox = g:localvimrc_sandbox
52 endif
53
54 " define default "localvimrc_ask" {{{2
55 " copy to script local variable to prevent .lvimrc disabling the sandbox
56 " again.
57 if (!exists("g:localvimrc_ask"))
58   let s:localvimrc_ask = 1
59 else
60   let s:localvimrc_ask = g:localvimrc_ask
61 endif
62
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
67 else
68   let s:localvimrc_whitelist = g:localvimrc_whitelist
69 endif
70
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
75 else
76   let s:localvimrc_blacklist = g:localvimrc_blacklist
77 endif
78
79 " initialize answer dictionary {{{2
80 let s:localvimrc_answers = {}
81
82 " initialize checksum dictionary {{{2
83 let s:localvimrc_checksums = {}
84
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
89 else
90   let s:localvimrc_persistent = g:localvimrc_persistent
91 endif
92
93 " define default "localvimrc_debug" {{{2
94 if (!exists("g:localvimrc_debug"))
95   let g:localvimrc_debug = 0
96 endif
97
98 " Section: Autocmd setup {{{1
99
100 if has("autocmd")
101   augroup localvimrc
102     autocmd!
103
104     " call s:LocalVimRC() when creating ore reading any file
105     autocmd VimEnter,BufNewFile,BufRead * call s:LocalVimRC()
106   augroup END
107 endif
108
109 " Section: Functions {{{1
110
111 " Function: s:LocalVimRC() {{{2
112 "
113 " search all local vimrc files from current directory up to root directory and
114 " source them in reverse order.
115 "
116 function! s:LocalVimRC()
117   " begin marker
118   call s:LocalVimRCDebug(1, "==================================================")
119
120   " print version
121   call s:LocalVimRCDebug(1, "localvimrc.vim " . g:loaded_localvimrc)
122
123   " read persistent information
124   call s:LocalVimRCReadPersistent()
125
126   " only consider normal buffers (skip especially CommandT's GoToFile buffer)
127   if (&buftype != "")
128     call s:LocalVimRCDebug(1, "not a normal buffer, exiting")
129     return
130   endif
131
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())
136   endif
137   call s:LocalVimRCDebug(2, "searching directory \"" . l:directory . "\"")
138
139   " generate a list of all local vimrc files with absolute file names along path to root
140   let l:absolute = {}
141   for l:rcfile in findfile(s:localvimrc_name, l:directory . ";", -1)
142     let l:absolute[resolve(fnamemodify(l:rcfile, ":p"))] = ""
143   endfor
144   let l:rcfiles = sort(keys(l:absolute))
145   call s:LocalVimRCDebug(1, "found files: " . string(l:rcfiles))
146
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)
150   endif
151   call s:LocalVimRCDebug(1, "candidate files: " . string(l:rcfiles))
152
153   " source all found local vimrc files along path from root (reverse order)
154   let l:answer = ""
155   for l:rcfile in l:rcfiles
156     call s:LocalVimRCDebug(2, "processing \"" . l:rcfile . "\"")
157     let l:rcfile_load = "unknown"
158
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"
165         endif
166       endif
167
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"
173         endif
174       endif
175
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] . "\"")
180
181           " check the answer
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"
186           endif
187         else
188           call s:LocalVimRCDebug(2, "checksum mismatch, no answer reuse")
189         endif
190       endif
191
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")
197             let l:answer = ""
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) "
203               else
204                 let l:message = "localvimrc: source " . l:rcfile . "? ([y]es/[n]o/[a]ll/[q]uit) "
205               endif
206               let l:answer = input(l:message)
207               call s:LocalVimRCDebug(2, "answer is \"" . l:answer . "\"")
208             endwhile
209           endif
210
211           " make answer upper case if persistence is 2 ("force")
212           if (s:localvimrc_persistent == 2)
213             let l:answer = toupper(l:answer)
214           endif
215
216           " store y/n answers
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"
225           endif
226
227           " check the answer
228           if (l:answer =~? '^[ya]$')
229             let l:rcfile_load = "yes"
230           elseif (l:answer =~? "^q$")
231             call s:LocalVimRCDebug(1, "ended processing files")
232             break
233           endif
234         endif
235       endif
236
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"
241         endif
242       endif
243
244       " should this rc file be loaded?
245       if (l:rcfile_load == "yes")
246         let l:command = "silent "
247
248         " add 'sandbox' if requested
249         if (s:localvimrc_sandbox != 0)
250           let l:command .= "sandbox "
251           call s:LocalVimRCDebug(2, "using sandbox")
252         endif
253         let l:command .= "source " . fnameescape(l:rcfile)
254
255         " execute the command
256         exec l:command
257         call s:LocalVimRCDebug(1, "sourced " . l:rcfile)
258
259       else
260         call s:LocalVimRCDebug(1, "skipping " . l:rcfile)
261       endif
262
263       " calculate checksum for each processed file
264       call s:LocalVimRCCalcChecksum(l:rcfile)
265
266     endif
267   endfor
268
269   " clear command line
270   redraw!
271
272   " make information persistent
273   call s:LocalVimRCWritePersistent()
274
275   " end marker
276   call s:LocalVimRCDebug(1, "==================================================")
277 endfunction
278
279 " Function: s:LocalVimRCCalcChecksum(filename) {{{2
280 "
281 " calculate checksum and store it in dictionary
282 "
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
287
288   call s:LocalVimRCDebug(3, "checksum calc -> ".l:file . " : " . l:checksum)
289 endfunction
290
291 " Function: s:LocalVimRCCheckChecksum(filename) {{{2
292 "
293 " Check checksum in dictionary. Return "0" if it does not exist, "1" otherwise
294 "
295 function! s:LocalVimRCCheckChecksum(filename)
296   let l:return = 0
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])
302
303     if (s:localvimrc_checksums[l:file] == l:checksum)
304       let l:return = 1
305     endif
306
307   endif
308
309   return l:return
310 endfunction
311
312 " Function: s:LocalVimRCReadPersistent() {{{2
313 "
314 " read decision variables from global variable
315 "
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]
323         endfor
324         call s:LocalVimRCDebug(3, "read answer persistent data: " . string(s:localvimrc_answers))
325       endif
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))
330       endif
331     endif
332   endif
333 endfunction
334
335 " Function: s:LocalVimRCWritePersistent() {{{2
336 "
337 " write decision variables to global variable to make them persistent
338 "
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]
346     endfor
347
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))
355       else
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")
358       endif
359     endif
360   else
361     if exists("g:LOCALVIMRC_ANSWERS")
362       unlet g:LOCALVIMRC_ANSWERS
363       call s:LocalVimRCDebug(3, "deleted answer persistent data")
364     endif
365     if exists("g:LOCALVIMRC_CHECKSUMS")
366       unlet g:LOCALVIMRC_CHECKSUMS
367       call s:LocalVimRCDebug(3, "deleted checksum persistent data")
368     endif
369
370   endif
371 endfunction
372
373 " Function: s:LocalVimRCClear() {{{2
374 "
375 " clear all stored data
376 "
377 function! s:LocalVimRCClear()
378   if exists("s:localvimrc_answers")
379     unlet s:localvimrc_answers
380     call s:LocalVimRCDebug(3, "deleted answer local data")
381   endif
382   if exists("s:localvimrc_checksums")
383     unlet s:localvimrc_checksums
384     call s:LocalVimRCDebug(3, "deleted checksum local data")
385   endif
386   if exists("g:LOCALVIMRC_ANSWERS")
387     unlet g:LOCALVIMRC_ANSWERS
388     call s:LocalVimRCDebug(3, "deleted answer persistent data")
389   endif
390   if exists("g:LOCALVIMRC_CHECKSUMS")
391     unlet g:LOCALVIMRC_CHECKSUMS
392     call s:LocalVimRCDebug(3, "deleted checksum persistent data")
393   endif
394 endfunction
395
396 " Function: s:LocalVimRCError(text) {{{2
397 "
398 " output error message
399 "
400 function! s:LocalVimRCError(text)
401   echohl ErrorMsg | echo "localvimrc: " . a:text | echohl None
402 endfunction
403
404 " Function: s:LocalVimRCDebug(level, text) {{{2
405 "
406 " output debug message, if this message has high enough importance
407 "
408 function! s:LocalVimRCDebug(level, text)
409   if (g:localvimrc_debug >= a:level)
410     echom "localvimrc: " . a:text
411   endif
412 endfunction
413
414 " Section: Commands {{{1
415 command! LocalVimRCClear call s:LocalVimRCClear()
416
417 " vim600: foldmethod=marker foldlevel=0 :