/
vam.vim
541 lines (463 loc) · 20.2 KB
/
vam.vim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
" see README
" this file contains code which is always used
" code which is used for installing / updating etc should go into vam/install.vim
" don't need a plugin. If you want to use this plugin you call Activate once
" anyway
fun! vam#DefineAndBind(local,global,default)
return 'if !exists('.string(a:global).') | let '.a:global.' = '.a:default.' | endif | let '.a:local.' = '.a:global
endfun
" assign g:os
for os in split('amiga beos dos32 dos16 mac macunix os2 qnx unix vms win16 win32 win64 win32unix', ' ')
if has(os) | let g:os = os | break | endif
endfor
let g:is_win = g:os[:2] == 'win'
exec vam#DefineAndBind('s:c','g:vim_addon_manager','{}')
let s:c.auto_install = get(s:c,'auto_install', 0)
" repository locations:
let s:c.plugin_sources = get(s:c,'plugin_sources', {})
" if a plugin has an item here the dict value contents will be written as plugin info file
" Note: VAM itself may be added after definition of vam#PluginDirFromName
" function
let s:c.activated_plugins = get(s:c,'activated_plugins', {})
let s:c.create_addon_info_handlers = get(s:c, 'create_addon_info_handlers', 1)
" Users may install VAM system wide. In that case s:d is not writeable.
let s:d = expand('<sfile>:h:h:h')
let s:c.plugin_root_dir = get(s:c, 'plugin_root_dir', filewritable(s:d) ? s:d : '~/.vim/vim-addons')
unlet s:d
if s:c.plugin_root_dir == expand('~')
echohl Error
echomsg "VAM: Don't install VAM into ~/.vim the normal way. See docs -> SetupVAM function. Put it int ~/.vim/vim-addons/vim-addon-manager for example."
echohl None
finish
endif
" ensure we have absolute paths (windows doesn't like ~/.. ) :
let s:c.plugin_root_dir = expand(s:c.plugin_root_dir)
let s:c.dont_source = get(s:c, 'dont_source', 0)
let s:c.plugin_dir_by_name = get(s:c, 'plugin_dir_by_name', 'vam#DefaultPluginDirFromName')
let s:c.addon_completion_lhs = get(s:c, 'addon_completion_lhs', '<C-x><C-p>')
let s:c.debug_activation = get(s:c, 'debug_activation', 0)
let s:c.pool_item_check_fun = get(s:c, 'pool_item_check_fun', 'none')
let s:c.no_activate_hack = get(s:c, 'no_activate_hack', 0)
" experimental: will be documented when its tested
" don't echo lines, add them to a buffer to prevent those nasty "Press Enter"
" to show more requests by Vim
" TODO: move log code into other file (such as utils.vim) because its not used on each startup
" TODO: think about autowriting it
let s:c.log_to_buf = get(s:c, 'log_to_buf', 0)
let s:c.log_buffer_name = get(s:c, 'log_buffer_name', s:c.plugin_root_dir.'/VAM_LOG.txt')
" More options that are used for plugins’ installation are listed in
" autoload/vam/install.vim
if g:is_win && has_key(s:c, 'binary_utils')
" if binary-utils path exists then add it to PATH
let s:c.binary_utils = get(s:c,'binary_utils', s:c.plugin_root_dir.'\binary-utils')
let s:c.binary_utils_bin = s:c.binary_utils.'\dist\bin'
if isdirectory(s:c.binary_utils)
let $PATH=$PATH.';'.s:c.binary_utils_bin
endif
endif
" additional plugin sources should go into your .vimrc or into the repository
" called "vim-addon-manager-known-repositories" referenced here:
if executable('git')
let s:c.plugin_sources["vim-addon-manager-known-repositories"] = {'type' : 'git', 'url': 'git://github.com/MarcWeber/vim-addon-manager-known-repositories'}
else
let s:c.plugin_sources["vim-addon-manager-known-repositories"] = {'type' : 'archive', 'url': 'http://github.com/MarcWeber/vim-addon-manager-known-repositories/tarball/master', 'archive_name': 'vim-addon-manager-known-repositories-tip.tar.gz'}
endif
if s:c.create_addon_info_handlers
augroup VAM_addon_info_handlers
autocmd!
autocmd BufRead,BufNewFile *-addon-info.txt,addon-info.json
\ setlocal ft=addon-info
\ | setlocal syntax=json
\ | syn match Error "^\s*'"
autocmd BufWritePost *-addon-info.txt,addon-info.json call vam#ReadAddonInfo(expand('%'))
augroup END
endif
fun! vam#VerifyIsJSON(s)
" You must allow single-quoted strings in order for writefile([string()]) that
" adds missing addon information to work
let scalarless_body = substitute(a:s, '\v\"%(\\.|[^"\\])*\"|\''%(\''{2}|[^''])*\''|true|false|null|[+-]?\d+%(\.\d+%([Ee][+-]?\d+)?)?', '', 'g')
return scalarless_body !~# "[^,:{}[\\] \t]"
endfun
" use join so that you can break the dict into multiple lines. This makes
" reading it much easier
fun! vam#ReadAddonInfo(path)
" don't add "b" because it'll read dos files as "\r\n" which will fail the
" check and evaluate in eval. \r\n is checked out by some msys git
" versions with strange settings
" using eval is evil!
let body = join(readfile(a:path),"")
if vam#VerifyIsJSON(body)
let true=1
let false=0
let null=''
" using eval is now safe!
return eval(body)
else
call vam#Log( "Invalid JSON in ".a:path."!")
return {}
endif
endfun
fun! vam#DefaultPluginDirFromName(name)
" this function maps addon names to their storage location. \/: are replaced
" by - (See name rewriting)
return s:c.plugin_root_dir.'/'.substitute(a:name, '[\\/:]\+', '-', 'g')
endfun
fun! vam#PluginDirFromName(...)
return call(s:c.plugin_dir_by_name, a:000, {})
endfun
fun! vam#PluginRuntimePath(name)
let info = vam#AddonInfo(a:name)
return vam#PluginDirFromName(a:name).(has_key(info, 'runtimepath') ? '/'.info.runtimepath : '')
endfun
" adding VAM, so that its contained in list passed to :UpdateActivatedAddons
if filewritable(vam#PluginDirFromName('vim-addon-manager'))==2
let s:c.activated_plugins['vim-addon-manager']=1
endif
" doesn't check dependencies!
fun! vam#IsPluginInstalled(name)
let d = vam#PluginDirFromName(a:name)
" this will be dropped in about 12 months which is end of 2012
let old_path=s:c.plugin_root_dir.'/'.substitute(a:name, '[\\/:]\+', '', 'g')
if d != old_path && isdirectory(old_path)
if confirm("VAM has changed addon names policy for name rewriting. Rename ".old_path." to ".d."?", "&Ok") == 1
call rename(old_path, d)
endif
endif
" if dir exists and its not a failed download
" (empty archive directory)
return isdirectory(d)
\ && (!isdirectory(d.'/archive')
\ || !empty(glob(fnameescape(d).'/archive/*', 1)))
endfun
" {} if file doesn't exist
fun! vam#AddonInfo(name)
let infoFile = vam#AddonInfoFile(a:name)
return filereadable(infoFile)
\ ? vam#ReadAddonInfo(infoFile)
\ : {}
endfun
" opts: {
" 'plugin_sources': additional sources (used when installing dependencies)
" 'auto_install': when 1 overrides global setting, so you can autoinstall
" trusted repositories only
" }
fun! vam#ActivateRecursively(list_of_names, ...)
let opts = extend({'run_install_hooks': 1}, a:0 == 0 ? {} : a:1)
for name in a:list_of_names
if !has_key(s:c.activated_plugins, name)
" break circular dependencies..
let s:c.activated_plugins[name] = 0
let infoFile = vam#AddonInfoFile(name)
if !filereadable(infoFile) && !vam#IsPluginInstalled(name)
if empty(vam#install#Install([name], opts))
unlet s:c.activated_plugins[name]
continue
endif
endif
let info = vam#AddonInfo(name)
let dependencies = get(info,'dependencies', {})
" activate dependencies merging opts with given repository sources
" sources given in opts will win
call vam#ActivateAddons(keys(dependencies),
\ extend(copy(opts), {
\ 'plugin_sources' : extend(copy(dependencies), get(opts, 'plugin_sources',{})),
\ 'requested_by' : [name] + get(opts, 'requested_by', [])
\ }))
" source plugin/* files ?
let rtp = vam#PluginRuntimePath(name)
call add(opts.new_runtime_paths, rtp)
let opts.path_plugins[rtp] = name
if !empty(get(opts, 'requested_by'))
let opts.are_dependencies[name] = 1
endif
let s:c.activated_plugins[name] = 1
if s:c.debug_activation
" activation takes place later (-> new_runtime_paths), but messages will be in order
" XXX Lengths of “as it was requested by” and “which was requested by”
" match
call vam#Log('Will activate '.name.(empty(get(opts, 'requested_by'))?
\ (' as it was specified by user.'):
\ ("\n as it was requested by ".
\ join(opts.requested_by, "\n which was requested by ").'.')))
endif
endif
endfor
endfun
let s:top_level = 0
" see also ActivateRecursively
" Activate activates the plugins and their dependencies recursively.
" I sources both: plugin/*.vim and after/plugin/*.vim files when called after
" .vimrc has been sourced which happens when you activate plugins manually.
fun! vam#ActivateAddons(...) abort
let args = copy(a:000)
if a:0 == 0 | return | endif
if type(args[0])==type("")
" way of usage 1: pass addon names as function arguments
" Example: ActivateAddons("name1","name2")
" This way of calling has two flaws:
" - doesn't scale due to amount of args limitation
" - you can't pass autoinstall=1
" Therefore we should get rid of this way..
" verify that all args are strings only because errors are hard to debug
if !empty(filter(copy(args),'type(v:val) != type("")'))
throw "Bad argument to vam#ActivateAddons: only Strings are permitted. Use ActivateAddons(['n1','n2',..], {..}) to pass options dictionary"
endif
let args=[args, {}]
else
" way of usage 2: pass addon names as list optionally passing options
" Example: ActivateAddons(["name1","name2"], { options })
let args=[args[0], get(args,1,{})]
endif
" now opts should be defined
" args[0] = plugin names
" args[1] = options
let opts = args[1]
let topLevel = !has_key(opts, 'new_runtime_paths')
" add new_runtime_paths state if not present in opts yet
let new_runtime_paths = get(opts, 'new_runtime_paths', [])
let to_be_activated = get(opts, 'to_be_activated', {})
let path_plugins = get(opts, 'path_plugins', {})
let are_dependencies = get(opts, 'are_dependencies', {})
let opts.new_runtime_paths = new_runtime_paths
let opts.to_be_activated = to_be_activated
let opts.path_plugins = path_plugins
let opts.are_dependencies = are_dependencies
for a in args[0]
let to_be_activated[a] = 1
endfor
call call('vam#ActivateRecursively', args)
if topLevel
if exists('g:vam_plugin_whitelist')
call filter(to_be_activated, 'index(g:vam_plugin_whitelist, v:key) != -1 || has_key(are_dependencies, v:key)')
call filter(path_plugins, 'has_key(to_be_activated, v:val)')
call filter(new_runtime_paths, 'has_key(path_plugins, v:val)')
endif
" deferred tasks:
" - add addons to runtimepath
" - add source plugin/**/*.vim files in case Activate was called long
" after .vimrc has been sourced
" add paths after ~/.vim but before $VIMRUNTIME
" don't miss the after directories if they exist and
" put them last! (Thanks to Oliver Teuliere)
let rtp = split(&runtimepath, '\v(\\@<!(\\.)*\\)@<!\,')
let escapeComma = 'escape(v:val, '','')'
let after = filter(map(copy(new_runtime_paths), 'v:val."/after"'), 'isdirectory(v:val)')
if !s:c.dont_source
let &runtimepath=join(rtp[:0] + map(copy(new_runtime_paths), escapeComma)
\ + rtp[1:]
\ + map(after, escapeComma),
\ ",")
endif
unlet rtp
for rtp in new_runtime_paths
" filetype off/on would do the same ?
call vam#GlobThenSource(rtp.'/ftdetect/*.vim')
endfor
" using force is very likely to cause the plugin to be sourced twice
" I hope the plugins don't mind
if !has('vim_starting') || get(opts, 'force_loading_plugins_now', 0)
for rtp in new_runtime_paths
call vam#GlobThenSource(rtp.'/plugin/**/*.vim')
call vam#GlobThenSource(rtp.'/after/plugin/**/*.vim')
endfor
if !empty(new_runtime_paths)
" The purpose of this line is to "refresh" buffer local vars and syntax.
" (eg when loading a python plugin when opening a .py file)
" Maybe its the responsibility of plugins to "refresh" settings of
" buffers which are already open - I don't expect them to do so.
" Let's see how much this breaks.
call map(filter(range(1, bufnr('$')),
\ 'bufexists(v:val)'),
\ 'setbufvar(v:val, "&filetype", getbufvar(v:val, "&filetype"))')
endif
endif
let failed = filter(keys(to_be_activated), '!has_key(s:c.activated_plugins, v:val)')
if !empty(failed)
throw 'These plugins could not be activated for some reason: '.string(failed)
endif
endif
endfun
fun! vam#DisplayAddonInfoLines(name, repository)
let name = a:name
let repository = a:repository
let lines = []
call add(lines, 'Plugin: '.name.((has_key(repository, 'version'))?(' version '.repository.version):('')))
if has_key(repository, 'vim_script_nr')
call add(lines, 'Script number: '.repository.vim_script_nr)
call add(lines, 'Vim.org page: http://www.vim.org/scripts/script.php?script_id='.repository.vim_script_nr)
endif
if has_key(repository, 'homepage')
call add(lines, 'Home page: '.repository.homepage)
elseif repository.url =~? '^\w\+://github\.com/'
call add(lines, 'Home page: https://github.com/'.substitute(repository.url, '^\V\w\+://github.com/\v([^/]+\/[^/]{-}%(\.git)?)%(\/|$)@=.*', '\1', ''))
elseif repository.url =~? '^\w\+://bitbucket\.org/'
call add(lines, 'Home page: https://bitbucket.org/'.substitute(repository.url, '^\V\w\+://bitbucket.org/\v([^/]+\/[^/]+).*', '\1', ''))
endif
call add(lines, 'Source URL: '.repository.url.' (type '.get(repository, 'type', 'archive').')',)
for key in filter(keys(repository), 'v:val!~#''\vurl|vim_script_nr|version|type|homepage''')
call add(lines, key.': '.string(repository[key]))
endfor
return lines
endfun
fun! vam#DisplayAddonInfo(name)
let repository = get(g:vim_addon_manager.plugin_sources, a:name, {})
let name = a:name
if empty(repository) && a:name =~ '^\d\+$'
" try to find by script id
let dict = filter(copy(g:vim_addon_manager.plugin_sources), 'get(v:val,"vim_script_nr","")."" == '.string(1*a:name))
if (empty(dict))
throw "unknown script ".a:name
else
let repository = get(values(dict), 0, {})
let name = keys(dict)[0]
endif
endif
if empty(repository)
echo "Invalid plugin name: " . a:name
return
endif
call vam#Log(repeat('=', &columns-1), 'Comment')
call vam#Log(join(vam#DisplayAddonInfoLines(name, repository),"\n"), 'None')
endfun
fun! vam#DisplayAddonsInfo(names)
call vam#install#LoadPool()
for name in a:names
call vam#DisplayAddonInfo(name)
endfor
endfun
fun! vam#SourceFiles(fs)
for file in a:fs
exec 'source '.fnameescape(file)
endfor
endfun
" FIXME won't list hidden files as well
if v:version>703 || (v:version==703 && has('patch465'))
fun! vam#GlobList(glob)
return glob(a:glob, 1, 1)
endfun
else
fun! vam#GlobList(glob)
return split(glob(a:glob, 1), "\n")
endfun
endif
fun! vam#GlobInDir(dir, glob)
return vam#GlobList(fnameescape(a:dir).'/'.a:glob)
endfun
fun! vam#GlobThenSource(glob)
if s:c.dont_source | return | endif
call vam#SourceFiles(vam#GlobList(a:glob))
endfun
if !s:c.no_activate_hack
augroup VIM_PLUGIN_MANAGER
autocmd VimEnter * call vam#SourceMissingPlugins()
augroup END
endif
" taken from tlib
fun! vam#OutputAsList(command) "{{{3
" let lines = ''
redir => lines
silent! exec a:command
redir END
return split(lines, '\n')
endfun
" hack: Vim sources plugin files after sourcing .vimrc
" Vim doesn't source the after/plugin/*.vim files in other runtime
" paths. So do this *after* plugin/* files have been sourced
"
" If you activate addons in plugin/*.vim files Vim will miss
" plugin/*.vim files of those files - so make sure they are alle sourced
"
" This function takes about 1ms to execute my system
fun! vam#SourceMissingPlugins()
" files which should have been sourced:
let fs = []
let rtp = split(&runtimepath, '\v(\\@<!(\\.)*\\)@<!\,')
for r in rtp | call extend(fs, vam#GlobInDir(r, 'plugin/*.vim')) | endfor
for r in rtp | call extend(fs, vam#GlobInDir(r, 'after/plugin/**/*.vim')) | endfor
let scriptnames = map(vam#OutputAsList('scriptnames'), 'v:val[(stridx(v:val,":")+2):-1]')
call filter(fs, 'index(scriptnames, v:val) == -1')
call vam#SourceFiles(fs)
endfun
fun! vam#AddonInfoFile(name)
" history:
" 1) plugin-info.txt was the first name (deprecated)
" 2) a:name-addon-info.txt was the second recommended name (maybe deprecated - no hurry)
" 3) Now the recommended way is addon-info.json because:
" - you can rename a script without having to rename the file
" - json says all about its contents (Let's hope all browsers still render
" it in a readable way
let p = vam#PluginDirFromName(a:name)
let default = p.'/addon-info.json'
let choices = [ default , p.'/plugin-info.txt', p.'/'.a:name.'-addon-info.txt']
for f in choices
if filereadable(f)
return f
endif
endfor
return default
endfun
" looks like an error but is not. Catches users attention. Logs to :messages
fun! vam#Log(s, ...)
if s:c.log_to_buf
let nr = bufnr(s:c.log_buffer_name)
if nr == -1
" create buffer and add date header
execute 'split' fnameescape(s:c.log_buffer_name)
cal append('$', '>>>>>>>>>>>> '.strftime('%c'))
autocmd! BufDelete <buffer> w
" on quit BufDelete is not run!!
autocmd! VimLeave <buffer> w
else
exec 'b '.nr
endif
cal append('$', split(a:s, "\n", 1))
" if the buffer appears to be modified vim asks questions when quitting,
" I want it to be silent, it gets written bi au command see above
" yes - if vim crashes logs are lost. I hope it doesn't happen to often.
" Writing on each messages seems overkill to me - The log may get long
" over time
setlocal nomodified
else
let hi = a:0 > 0 ? a:1 : 'WarningMsg'
exec 'echohl '. hi
for l in split(a:s, "\n", 1)
if empty(l)
echom ' '
else
echom l
endif
endfor
echohl None
endif
endfun
" If you want these commands witohut activating plugins call
" vam#ActivateAddons([]) with empty list. Not moving them into plugin/vam.vim
" to prevent additional IO seeks.
" its likely that the command names change introducing nice naming sheme
" Not sure which is best. Options:
" 1) *VAM 2) Addon* 3) VAM*
" 3 seems to be best but is more to type.
" Using 1) you can still show all commands by :*VAM<c-d> but this scheme is
" less common. So 2) is my favorite right now. I'm too lazy to break things at
command! -nargs=* -bar -complete=customlist,vam#install#NotInstalledAddonCompletion InstallAddons :call vam#install#Install([<f-args>])
command! -nargs=* -bar -complete=customlist,vam#install#AddonCompletion ActivateAddons :call vam#ActivateAddons([<f-args>])
command! -nargs=* -bar -complete=customlist,vam#install#AddonCompletion AddonsInfo :call vam#DisplayAddonsInfo([<f-args>])
command! -nargs=* -bar -complete=customlist,vam#install#InstalledAddonCompletion ActivateInstalledAddons :call vam#ActivateAddons([<f-args>])
command! -nargs=* -bar -complete=customlist,vam#install#UpdateCompletion UpdateAddons :call vam#install#Update([<f-args>])
command! -nargs=0 -bar UpdateActivatedAddons exec 'UpdateAddons '.join(keys(g:vim_addon_manager.activated_plugins),' ')
command! -nargs=* -bar -complete=customlist,vam#install#UninstallCompletion UninstallNotLoadedAddons :call vam#install#UninstallAddons([<f-args>])
command! -nargs=* -complete=customlist,vam#bisect#BisectCompletion AddonsBisect :call vam#bisect#Bisect(<f-args>)
fun! s:RunInstallHooks(plugins)
for name in a:plugins
call vam#install#RunHook('post-install', vam#AddonInfo(name), vam#install#GetRepo(name, {}), vam#PluginDirFromName(name), {})
endfor
endfun
command! -nargs=+ -complete=customlist,vam#install#InstalledAddonCompletion RunInstallHooks :call s:RunInstallHooks([<f-args>])
" plugin name completion function:
if !empty(s:c.addon_completion_lhs)
augroup VAM_addon_name_completion
autocmd!
execute 'autocmd FileType vim inoremap <buffer> <expr> '.s:c.addon_completion_lhs.' vam#utils#CompleteWith("vam#install#CompleteAddonName")'
augroup END
endif
" vim: et ts=8 sts=2 sw=2