vim-plug/plug.vim

2010 lines
55 KiB
VimL
Raw Normal View History

2013-09-10 16:58:41 +02:00
" vim-plug: Vim plugin manager
" ============================
"
" Download plug.vim and put it in ~/.vim/autoload
"
" mkdir -p ~/.vim/autoload
2013-09-11 05:06:57 +02:00
" curl -fLo ~/.vim/autoload/plug.vim \
" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
2013-09-10 16:58:41 +02:00
"
" Edit your .vimrc
"
2014-07-26 15:49:18 +02:00
" call plug#begin('~/.vim/plugged')
2013-09-10 16:58:41 +02:00
"
2014-07-26 15:49:18 +02:00
" " Make sure you use single quotes
2013-09-30 18:32:34 +02:00
" Plug 'junegunn/seoul256.vim'
" Plug 'junegunn/vim-easy-align'
2014-07-26 15:49:18 +02:00
"
" " On-demand loading
" Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' }
" Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
"
" " Using git URL
" Plug 'https://github.com/junegunn/vim-github-dashboard.git'
"
" " Plugin options
" Plug 'nsf/gocode', { 'tag': 'go.weekly.2012-03-13', 'rtp': 'vim' }
"
2014-07-30 13:00:21 +02:00
" " Plugin outside ~/.vim/plugged with post-update hook
" Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': 'yes \| ./install' }
"
" " Unmanaged plugin (manually installed and updated)
" Plug '~/my-prototype-plugin'
2013-09-10 16:58:41 +02:00
"
2013-09-16 10:37:09 +02:00
" call plug#end()
"
2014-07-27 04:06:47 +02:00
" Then reload .vimrc and :PlugInstall to install plugins.
" Visit https://github.com/junegunn/vim-plug for more information.
2013-09-10 16:58:41 +02:00
"
"
" Copyright (c) 2014 Junegunn Choi
2013-09-10 16:58:41 +02:00
"
" MIT License
"
" Permission is hereby granted, free of charge, to any person obtaining
" a copy of this software and associated documentation files (the
" "Software"), to deal in the Software without restriction, including
" without limitation the rights to use, copy, modify, merge, publish,
" distribute, sublicense, and/or sell copies of the Software, and to
" permit persons to whom the Software is furnished to do so, subject to
" the following conditions:
"
" The above copyright notice and this permission notice shall be
" included in all copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
if exists('g:loaded_plug')
finish
endif
let g:loaded_plug = 1
let s:cpo_save = &cpo
set cpo&vim
2014-10-10 08:48:17 +02:00
let s:plug_src = 'https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
2014-10-09 12:55:36 +02:00
let s:plug_tab = get(s:, 'plug_tab', -1)
let s:plug_buf = get(s:, 'plug_buf', -1)
let s:mac_gui = has('gui_macvim') && has('gui_running')
2013-09-10 16:58:41 +02:00
let s:is_win = has('win32') || has('win64')
let s:py2 = has('python') && !s:is_win
let s:ruby = has('ruby') && (v:version >= 703 || v:version == 702 && has('patch374'))
2015-01-17 22:38:50 +01:00
let s:nvim = has('nvim') && !s:is_win
let s:me = resolve(expand('<sfile>:p'))
let s:base_spec = { 'branch': 'master', 'frozen': 0 }
let s:TYPE = {
\ 'string': type(''),
\ 'list': type([]),
\ 'dict': type({}),
\ 'funcref': type(function('call'))
\ }
let s:loaded = get(s:, 'loaded', {})
let s:triggers = get(s:, 'triggers', {})
2013-09-10 16:58:41 +02:00
2013-09-16 10:37:09 +02:00
function! plug#begin(...)
if a:0 > 0
let s:plug_home_org = a:1
2014-09-17 12:41:00 +02:00
let home = s:path(fnamemodify(expand(a:1), ':p'))
elseif exists('g:plug_home')
let home = s:path(g:plug_home)
elseif !empty(&rtp)
let home = s:path(split(&rtp, ',')[0]) . '/plugged'
else
return s:err('Unable to determine plug home. Try calling plug#begin() with a path argument.')
endif
2013-09-10 16:58:41 +02:00
let g:plug_home = home
let g:plugs = {}
let g:plugs_order = []
let s:triggers = {}
2013-09-10 16:58:41 +02:00
call s:define_commands()
return 1
endfunction
function! s:define_commands()
command! -nargs=+ -bar Plug call s:add(<args>)
if !executable('git')
return s:err('`git` executable not found. vim-plug requires git.')
endif
command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install('<bang>' == '!', [<f-args>])
command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update('<bang>' == '!', [<f-args>])
2014-08-04 18:29:51 +02:00
command! -nargs=0 -bar -bang PlugClean call s:clean('<bang>' == '!')
command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif
2014-08-04 18:29:51 +02:00
command! -nargs=0 -bar PlugStatus call s:status()
command! -nargs=0 -bar PlugDiff call s:diff()
command! -nargs=? -bar PlugSnapshot call s:snapshot(<f-args>)
2013-09-10 16:58:41 +02:00
endfunction
2014-06-17 04:59:42 +02:00
function! s:to_a(v)
return type(a:v) == s:TYPE.list ? a:v : [a:v]
2014-06-17 04:59:42 +02:00
endfunction
2014-10-27 16:22:09 +01:00
function! s:to_s(v)
return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n"
endfunction
function! s:source(from, ...)
for pattern in a:000
2014-10-10 03:10:52 +02:00
for vim in s:lines(globpath(a:from, pattern))
execute 'source' s:esc(vim)
endfor
endfor
endfunction
function! s:assoc(dict, key, val)
let a:dict[a:key] = add(get(a:dict, a:key, []), a:val)
endfunction
2013-09-16 10:37:09 +02:00
function! plug#end()
if !exists('g:plugs')
return s:err('Call plug#begin() first')
endif
2014-06-17 04:59:42 +02:00
if exists('#PlugLOD')
augroup PlugLOD
autocmd!
augroup END
augroup! PlugLOD
endif
let lod = { 'ft': {}, 'map': {}, 'cmd': {} }
2014-06-17 04:59:42 +02:00
2013-09-16 10:37:09 +02:00
filetype off
for name in g:plugs_order
let plug = g:plugs[name]
if get(s:loaded, name, 0) || !has_key(plug, 'on') && !has_key(plug, 'for')
let s:loaded[name] = 1
2014-06-17 04:59:42 +02:00
continue
endif
2014-02-11 16:45:23 +01:00
if has_key(plug, 'on')
let s:triggers[name] = { 'map': [], 'cmd': [] }
2014-07-30 20:04:59 +02:00
for cmd in s:to_a(plug.on)
if cmd =~ '^<Plug>.\+'
if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i'))
call s:assoc(lod.map, cmd, name)
endif
call add(s:triggers[name].map, cmd)
elseif cmd =~ '^[A-Z]'
2014-12-07 21:19:09 +01:00
if exists(':'.cmd) != 2
call s:assoc(lod.cmd, cmd, name)
endif
call add(s:triggers[name].cmd, cmd)
2014-02-11 16:45:23 +01:00
endif
endfor
2014-06-17 04:59:42 +02:00
endif
if has_key(plug, 'for')
let types = s:to_a(plug.for)
if !empty(types)
call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim')
endif
for type in types
call s:assoc(lod.ft, type, name)
2014-06-22 06:09:18 +02:00
endfor
2013-09-16 10:37:09 +02:00
endif
endfor
2014-06-22 06:09:18 +02:00
for [cmd, names] in items(lod.cmd)
execute printf(
\ 'command! -nargs=* -range -bang %s call s:lod_cmd(%s, "<bang>", <line1>, <line2>, <q-args>, %s)',
\ cmd, string(cmd), string(names))
endfor
for [map, names] in items(lod.map)
for [mode, map_prefix, key_prefix] in
\ [['i', '<C-O>', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']]
execute printf(
\ '%snoremap <silent> %s %s:<C-U>call <SID>lod_map(%s, %s, "%s")<CR>',
\ mode, map, map_prefix, string(map), string(names), key_prefix)
endfor
endfor
for [ft, names] in items(lod.ft)
2014-06-22 06:09:18 +02:00
augroup PlugLOD
execute printf('autocmd FileType %s call <SID>lod_ft(%s, %s)',
\ ft, string(ft), string(names))
2014-06-22 06:09:18 +02:00
augroup END
endfor
2014-07-16 13:26:42 +02:00
call s:reorg_rtp()
2013-09-16 10:37:09 +02:00
filetype plugin indent on
if has('vim_starting')
syntax enable
else
call s:reload()
endif
2013-09-16 10:37:09 +02:00
endfunction
function! s:loaded_names()
return filter(copy(g:plugs_order), 'get(s:loaded, v:val, 0)')
endfunction
function! s:reload()
for name in s:loaded_names()
call s:source(s:rtp(g:plugs[name]), 'plugin/**/*.vim', 'after/plugin/**/*.vim')
endfor
endfunction
function! s:trim(str)
return substitute(a:str, '[\/]\+$', '', '')
endfunction
function! s:git_version_requirement(...)
let s:git_version = get(s:, 'git_version',
\ map(split(split(s:system('git --version'))[-1], '\.'), 'str2nr(v:val)'))
for idx in range(0, a:0 - 1)
let v = get(s:git_version, idx, 0)
if v < a:000[idx] | return 0
elseif v > a:000[idx] | return 1
endif
endfor
return 1
endfunction
function! s:progress_opt(base)
return a:base && !s:is_win &&
\ s:git_version_requirement(1, 7, 1) ? '--progress' : ''
endfunction
2014-07-09 17:44:09 +02:00
if s:is_win
function! s:rtp(spec)
return s:path(a:spec.dir . get(a:spec, 'rtp', ''))
2014-07-09 17:44:09 +02:00
endfunction
function! s:path(path)
return s:trim(substitute(a:path, '/', '\', 'g'))
2014-07-09 17:44:09 +02:00
endfunction
function! s:dirpath(path)
return s:path(a:path) . '\'
endfunction
function! s:is_local_plug(repo)
return a:repo =~? '^[a-z]:'
endfunction
2014-07-09 17:44:09 +02:00
else
function! s:rtp(spec)
return s:dirpath(a:spec.dir . get(a:spec, 'rtp', ''))
endfunction
function! s:path(path)
return s:trim(a:path)
2014-07-09 17:44:09 +02:00
endfunction
function! s:dirpath(path)
2014-07-29 14:21:11 +02:00
return substitute(a:path, '[/\\]*$', '/', '')
2014-07-09 17:44:09 +02:00
endfunction
function! s:is_local_plug(repo)
return a:repo[0] =~ '[/$~]'
endfunction
2014-07-09 17:44:09 +02:00
endif
2013-09-25 15:35:34 +02:00
function! s:err(msg)
echohl ErrorMsg
echom a:msg
echohl None
return 0
endfunction
function! s:esc(path)
return escape(a:path, ' ')
endfunction
function! s:escrtp(path)
return escape(a:path, ' ,')
endfunction
function! s:remove_rtp()
for name in s:loaded_names()
let rtp = s:rtp(g:plugs[name])
execute 'set rtp-='.s:escrtp(rtp)
let after = globpath(rtp, 'after')
if isdirectory(after)
execute 'set rtp-='.s:escrtp(after)
endif
endfor
2014-02-11 16:45:23 +01:00
endfunction
2014-07-16 13:26:42 +02:00
function! s:reorg_rtp()
if !empty(s:first_rtp)
execute 'set rtp-='.s:first_rtp
execute 'set rtp-='.s:last_rtp
endif
" &rtp is modified from outside
if exists('s:prtp') && s:prtp !=# &rtp
call s:remove_rtp()
unlet! s:middle
endif
let s:middle = get(s:, 'middle', &rtp)
let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])')
let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), 'isdirectory(v:val)')
let rtp = join(map(rtps, 'escape(v:val, ",")'), ',')
\ . ','.s:middle.','
\ . join(map(afters, 'escape(v:val, ",")'), ',')
let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g')
let s:prtp = &rtp
if !empty(s:first_rtp)
execute 'set rtp^='.s:first_rtp
2014-07-16 13:26:42 +02:00
execute 'set rtp+='.s:last_rtp
endif
endfunction
2014-08-09 05:59:04 +02:00
function! plug#load(...)
if a:0 == 0
return s:err('Argument missing: plugin name(s) required')
endif
if !exists('g:plugs')
2014-08-09 06:11:41 +02:00
return s:err('plug#begin was not called')
2014-08-09 05:59:04 +02:00
endif
let unknowns = filter(copy(a:000), '!has_key(g:plugs, v:val)')
if !empty(unknowns)
let s = len(unknowns) > 1 ? 's' : ''
return s:err(printf('Unknown plugin%s: %s', s, join(unknowns, ', ')))
end
for name in a:000
call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
2014-08-09 05:59:04 +02:00
endfor
doautocmd BufRead
2014-08-09 05:59:04 +02:00
return 1
endfunction
function! s:remove_triggers(name)
if !has_key(s:triggers, a:name)
return
endif
for cmd in s:triggers[a:name].cmd
execute 'silent! delc' cmd
endfor
for map in s:triggers[a:name].map
execute 'silent! unmap' map
execute 'silent! iunmap' map
endfor
call remove(s:triggers, a:name)
endfunction
function! s:lod(names, types)
for name in a:names
call s:remove_triggers(name)
let s:loaded[name] = 1
2014-02-11 16:45:23 +01:00
endfor
call s:reorg_rtp()
2014-06-22 06:09:18 +02:00
for name in a:names
let rtp = s:rtp(g:plugs[name])
for dir in a:types
call s:source(rtp, dir.'/**/*.vim')
endfor
2014-06-22 06:09:18 +02:00
endfor
endfunction
function! s:lod_ft(pat, names)
call s:lod(a:names, ['plugin', 'after/plugin'])
execute 'autocmd! PlugLOD FileType' a:pat
doautocmd filetypeplugin FileType
doautocmd filetypeindent FileType
2014-06-17 04:59:42 +02:00
endfunction
function! s:lod_cmd(cmd, bang, l1, l2, args, names)
call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args)
2014-02-11 16:45:23 +01:00
endfunction
function! s:lod_map(map, names, prefix)
call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin'])
let extra = ''
while 1
let c = getchar(0)
if c == 0
break
endif
let extra .= nr2char(c)
endwhile
call feedkeys(a:prefix . substitute(a:map, '^<Plug>', "\<Plug>", '') . extra)
endfunction
function! s:add(repo, ...)
2014-07-27 14:31:28 +02:00
if a:0 > 1
return s:err('Invalid number of arguments (1..2)')
2014-07-27 14:31:28 +02:00
endif
try
let repo = s:trim(a:repo)
let name = fnamemodify(repo, ':t:s?\.git$??')
let spec = extend(s:infer_properties(name, repo),
2014-07-30 20:04:59 +02:00
\ a:0 == 1 ? s:parse_options(a:1) : s:base_spec)
2014-07-27 14:31:28 +02:00
let g:plugs[name] = spec
let g:plugs_order += [name]
let s:loaded[name] = 0
2014-07-27 14:31:28 +02:00
catch
return s:err(v:exception)
2014-07-27 14:31:28 +02:00
endtry
endfunction
function! s:parse_options(arg)
let opts = copy(s:base_spec)
let type = type(a:arg)
if type == s:TYPE.string
let opts.branch = a:arg
elseif type == s:TYPE.dict
call extend(opts, a:arg)
if has_key(opts, 'tag')
2015-02-18 08:47:32 +01:00
let opts.tag = remove(opts, 'tag')
endif
if has_key(opts, 'dir')
let opts.dir = s:dirpath(expand(opts.dir))
endif
else
throw 'Invalid argument type (expected: string or dictionary)'
endif
2014-07-27 14:31:28 +02:00
return opts
endfunction
2014-07-09 17:44:09 +02:00
function! s:infer_properties(name, repo)
let repo = a:repo
2014-07-27 14:31:28 +02:00
if s:is_local_plug(repo)
2014-07-30 20:04:59 +02:00
return { 'dir': s:dirpath(expand(repo)) }
2013-09-17 05:35:10 +02:00
else
2014-07-27 14:31:28 +02:00
if repo =~ ':'
let uri = repo
else
2014-07-27 14:31:28 +02:00
if repo !~ '/'
let repo = 'vim-scripts/'. repo
endif
2014-08-25 18:36:50 +02:00
let fmt = get(g:, 'plug_url_format', 'https://git::@github.com/%s.git')
let uri = printf(fmt, repo)
2013-09-17 05:35:10 +02:00
endif
let dir = s:dirpath( fnamemodify(join([g:plug_home, a:name], '/'), ':p') )
2014-07-30 20:04:59 +02:00
return { 'dir': dir, 'uri': uri }
2013-09-10 16:58:41 +02:00
endif
2014-07-27 14:31:28 +02:00
endfunction
2013-09-10 16:58:41 +02:00
function! s:install(force, names)
call s:update_impl(0, a:force, a:names)
2013-09-10 16:58:41 +02:00
endfunction
function! s:update(force, names)
call s:update_impl(1, a:force, a:names)
2013-09-10 16:58:41 +02:00
endfunction
2014-08-09 05:02:03 +02:00
function! plug#helptags()
if !exists('g:plugs')
2014-08-09 06:11:41 +02:00
return s:err('plug#begin was not called')
2014-08-09 05:02:03 +02:00
endif
for spec in values(g:plugs)
2013-09-10 16:58:41 +02:00
let docd = join([spec.dir, 'doc'], '/')
if isdirectory(docd)
silent! execute 'helptags' s:esc(docd)
2013-09-10 16:58:41 +02:00
endif
endfor
2014-08-09 05:02:03 +02:00
return 1
2013-09-10 16:58:41 +02:00
endfunction
function! s:syntax()
syntax clear
2014-04-08 17:53:37 +02:00
syntax region plug1 start=/\%1l/ end=/\%2l/ contains=plugNumber
syntax region plug2 start=/\%2l/ end=/\%3l/ contains=plugBracket,plugX
syn match plugNumber /[0-9]\+[0-9.]*/ contained
syn match plugBracket /[[\]]/ contained
syn match plugX /x/ contained
2013-09-10 16:58:41 +02:00
syn match plugDash /^-/
syn match plugPlus /^+/
syn match plugStar /^*/
2014-07-26 15:49:18 +02:00
syn match plugMessage /\(^- \)\@<=.*/
syn match plugName /\(^- \)\@<=[^ ]*:/
syn match plugInstall /\(^+ \)\@<=[^:]*/
syn match plugUpdate /\(^* \)\@<=[^:]*/
2014-04-08 17:53:37 +02:00
syn match plugCommit /^ [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
syn match plugSha /\(^ \)\@<=[0-9a-z]\{7}/ contained
2014-04-08 17:53:37 +02:00
syn match plugRelDate /([^)]*)$/ contained
syn match plugNotLoaded /(not loaded)$/
syn match plugError /^x.*/
2014-01-06 05:04:38 +01:00
syn keyword Function PlugInstall PlugStatus PlugUpdate PlugClean
2013-09-10 16:58:41 +02:00
hi def link plug1 Title
hi def link plug2 Repeat
2013-12-04 15:41:53 +01:00
hi def link plugX Exception
2013-09-10 16:58:41 +02:00
hi def link plugBracket Structure
hi def link plugNumber Number
2013-09-10 16:58:41 +02:00
hi def link plugDash Special
hi def link plugPlus Constant
hi def link plugStar Boolean
2014-07-26 15:49:18 +02:00
hi def link plugMessage Function
2013-09-10 16:58:41 +02:00
hi def link plugName Label
hi def link plugInstall Function
hi def link plugUpdate Type
hi def link plugError Error
2014-04-08 14:43:49 +02:00
hi def link plugRelDate Comment
hi def link plugSha Identifier
hi def link plugNotLoaded Comment
2013-09-10 16:58:41 +02:00
endfunction
function! s:lpad(str, len)
return a:str . repeat(' ', a:len - len(a:str))
endfunction
function! s:lines(msg)
return split(a:msg, "[\r\n]")
endfunction
2014-01-06 05:04:38 +01:00
function! s:lastline(msg)
return get(s:lines(a:msg), -1, '')
2013-09-10 16:58:41 +02:00
endfunction
function! s:new_window()
2014-08-20 05:22:23 +02:00
execute get(g:, 'plug_window', 'vertical topleft new')
endfunction
2014-10-09 12:55:36 +02:00
function! s:plug_window_exists()
let buflist = tabpagebuflist(s:plug_tab)
return !empty(buflist) && index(buflist, s:plug_buf) >= 0
2014-10-09 12:55:36 +02:00
endfunction
function! s:switch_in()
if !s:plug_window_exists()
return 0
endif
2014-10-09 21:39:38 +02:00
if winbufnr(0) != s:plug_buf
let s:pos = [tabpagenr(), winnr(), winsaveview()]
execute 'normal!' s:plug_tab.'gt'
let winnr = bufwinnr(s:plug_buf)
execute winnr.'wincmd w'
call add(s:pos, winsaveview())
else
let s:pos = [winsaveview()]
endif
2014-10-09 12:55:36 +02:00
setlocal modifiable
2014-10-09 12:55:36 +02:00
return 1
endfunction
2014-10-10 08:48:17 +02:00
function! s:switch_out(...)
2014-10-09 21:39:38 +02:00
call winrestview(s:pos[-1])
2014-10-09 12:55:36 +02:00
setlocal nomodifiable
2014-10-10 08:48:17 +02:00
if a:0 > 0
execute a:1
endif
2014-10-09 21:39:38 +02:00
if len(s:pos) > 1
execute 'normal!' s:pos[0].'gt'
execute s:pos[1] 'wincmd w'
call winrestview(s:pos[2])
endif
2014-10-09 12:55:36 +02:00
endfunction
2013-09-10 16:58:41 +02:00
function! s:prepare()
call s:job_abort()
if s:switch_in()
silent %d _
2013-09-10 16:58:41 +02:00
else
call s:new_window()
nnoremap <silent> <buffer> q :if b:plug_preview==1<bar>pc<bar>endif<bar>echo<bar>q<cr>
nnoremap <silent> <buffer> R :silent! call <SID>retry()<cr>
nnoremap <silent> <buffer> D :PlugDiff<cr>
nnoremap <silent> <buffer> S :PlugStatus<cr>
nnoremap <silent> <buffer> U :call <SID>status_update()<cr>
xnoremap <silent> <buffer> U :call <SID>status_update()<cr>
nnoremap <silent> <buffer> ]] :silent! call <SID>section('')<cr>
nnoremap <silent> <buffer> [[ :silent! call <SID>section('b')<cr>
let b:plug_preview = -1
2014-10-09 12:55:36 +02:00
let s:plug_tab = tabpagenr()
let s:plug_buf = winbufnr(0)
2013-09-10 16:58:41 +02:00
call s:assign_name()
endif
silent! unmap <buffer> <cr>
silent! unmap <buffer> L
2015-02-17 06:09:07 +01:00
silent! unmap <buffer> o
silent! unmap <buffer> X
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline modifiable
2013-09-10 16:58:41 +02:00
setf vim-plug
call s:syntax()
endfunction
function! s:assign_name()
" Assign buffer name
let prefix = '[Plugins]'
let name = prefix
let idx = 2
while bufexists(name)
let name = printf('%s (%s)', prefix, idx)
2013-09-10 16:58:41 +02:00
let idx = idx + 1
endwhile
silent! execute 'f' fnameescape(name)
2013-09-10 16:58:41 +02:00
endfunction
function! s:do(pull, force, todo)
2014-07-26 15:49:18 +02:00
for [name, spec] in items(a:todo)
if !isdirectory(spec.dir)
continue
endif
2014-10-09 12:55:36 +02:00
let installed = has_key(s:update.new, name)
let updated = installed ? 0 :
2014-10-01 04:37:58 +02:00
\ (a:pull && !empty(s:system_chomp('git log --pretty=format:"%h" "HEAD...HEAD@{1}"', spec.dir)))
if a:force || installed || updated
2014-10-01 04:37:58 +02:00
execute 'cd' s:esc(spec.dir)
2014-07-26 15:49:18 +02:00
call append(3, '- Post-update hook for '. name .' ... ')
let type = type(spec.do)
if type == s:TYPE.string
try
" FIXME: Escaping is incomplete. We could use shellescape with eval,
" but it won't work on Windows.
let g:_plug_do = '!'.escape(spec.do, '#!%')
2014-07-26 20:16:44 +02:00
execute "normal! :execute g:_plug_do\<cr>\<cr>"
finally
let result = v:shell_error ? ('Exit status: '.v:shell_error) : 'Done!'
unlet g:_plug_do
endtry
elseif type == s:TYPE.funcref
2014-07-26 15:49:18 +02:00
try
let status = installed ? 'installed' : (updated ? 'updated' : 'unchanged')
call spec.do({ 'name': name, 'status': status, 'force': a:force })
2014-07-26 15:49:18 +02:00
let result = 'Done!'
catch
let result = 'Error: ' . v:exception
endtry
else
let result = 'Error: Invalid type!'
endif
call setline(4, getline(4) . result)
2014-10-01 04:37:58 +02:00
cd -
2014-07-26 15:49:18 +02:00
endif
endfor
endfunction
function! s:finish(pull)
let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen'))
if new_frozen
let s = new_frozen > 1 ? 's' : ''
call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s))
endif
2013-12-04 15:41:53 +01:00
call append(3, '- Finishing ... ')
2013-09-10 16:58:41 +02:00
redraw
2014-08-09 05:02:03 +02:00
call plug#helptags()
call plug#end()
2013-12-04 15:41:53 +01:00
call setline(4, getline(4) . 'Done!')
redraw
let msgs = []
2014-10-09 12:55:36 +02:00
if !empty(s:update.errors)
call add(msgs, "Press 'R' to retry.")
endif
if a:pull && len(s:update.new) < len(filter(getline(5, '$'),
\ "v:val =~ '^- ' && stridx(v:val, 'Already up-to-date') < 0"))
call add(msgs, "Press 'D' to see the updated changes.")
endif
echo join(msgs, ' ')
endfunction
function! s:retry()
2014-10-09 12:55:36 +02:00
if empty(s:update.errors)
return
endif
2014-10-09 12:55:36 +02:00
call s:update_impl(s:update.pull, s:update.force,
\ extend(copy(s:update.errors), [s:update.threads]))
2013-09-10 16:58:41 +02:00
endfunction
function! s:is_managed(name)
return has_key(g:plugs[a:name], 'uri')
endfunction
function! s:names(...)
2014-10-10 10:36:54 +02:00
return sort(filter(keys(g:plugs), 'stridx(v:val, a:1) == 0 && s:is_managed(v:val)'))
endfunction
function! s:update_impl(pull, force, args) abort
let args = copy(a:args)
let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ?
\ remove(args, -1) : get(g:, 'plug_threads', 16)
let managed = filter(copy(g:plugs), 's:is_managed(v:key)')
let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') :
\ filter(managed, 'index(args, v:key) >= 0')
if empty(todo)
echohl WarningMsg
echo 'No plugin to '. (a:pull ? 'update' : 'install') . '.'
echohl None
return
endif
2013-09-10 16:58:41 +02:00
if !s:is_win && s:git_version_requirement(2, 3)
let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : ''
let $GIT_TERMINAL_PROMPT = 0
for plug in values(todo)
let plug.uri = substitute(plug.uri,
\ '^https://git::@github\.com', 'https://github.com', '')
endfor
endif
if !isdirectory(g:plug_home)
try
call mkdir(g:plug_home, 'p')
catch
2014-12-04 07:12:00 +01:00
return s:err(printf('Invalid plug directory: %s. '.
2014-10-10 03:39:49 +02:00
\ 'Try to call plug#begin with a valid directory', g:plug_home))
endtry
endif
2014-10-09 12:55:36 +02:00
let s:update = {
\ 'start': reltime(),
\ 'all': todo,
\ 'todo': copy(todo),
\ 'errors': [],
\ 'pull': a:pull,
\ 'force': a:force,
\ 'new': {},
\ 'threads': (s:py2 || s:ruby || s:nvim) ? min([len(todo), threads]) : 1,
2014-10-09 12:55:36 +02:00
\ 'bar': '',
\ 'fin': 0
\ }
2013-09-10 16:58:41 +02:00
call s:prepare()
2014-10-09 12:55:36 +02:00
call append(0, ['', ''])
2014-10-10 03:39:49 +02:00
normal! 2G
2013-09-10 16:58:41 +02:00
if (s:py2 || s:ruby) && !s:nvim && s:update.threads > 1
try
let imd = &imd
if s:mac_gui
set noimd
endif
if s:ruby
call s:update_ruby()
else
call s:update_python()
endif
catch
let lines = getline(4, '$')
let printed = {}
silent! 4,$d _
for line in lines
2014-10-10 08:48:17 +02:00
let name = s:extract_name(line, '.', '')
if empty(name) || !has_key(printed, name)
call append('$', line)
2014-07-23 04:46:29 +02:00
if !empty(name)
let printed[name] = 1
2014-10-09 12:55:36 +02:00
if line[0] == 'x' && index(s:update.errors, name) < 0
call add(s:update.errors, name)
2014-07-23 04:46:29 +02:00
end
endif
endif
endfor
finally
let &imd = imd
2014-10-09 12:55:36 +02:00
call s:update_finish()
endtry
2013-09-10 16:58:41 +02:00
else
2014-10-09 12:55:36 +02:00
call s:update_vim()
2013-09-10 16:58:41 +02:00
endif
endfunction
2014-10-09 12:55:36 +02:00
function! s:update_finish()
if exists('s:git_terminal_prompt')
let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt
endif
2014-10-09 12:55:36 +02:00
if s:switch_in()
call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'has_key(v:val, "do")'))
call s:finish(s:update.pull)
call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.')
2014-10-10 08:48:17 +02:00
call s:switch_out('normal! gg')
2014-10-09 12:55:36 +02:00
endif
endfunction
function! s:job_abort()
if !s:nvim || !exists('s:jobs')
return
endif
augroup PlugJobControl
autocmd!
augroup END
for [name, j] in items(s:jobs)
silent! call jobstop(j.jobid)
if j.new
call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir))
endif
endfor
let s:jobs = {}
endfunction
2014-10-09 12:55:36 +02:00
function! s:job_handler(name) abort
if !s:plug_window_exists() " plug window closed
return s:job_abort()
endif
if !has_key(s:jobs, a:name)
2014-10-09 12:55:36 +02:00
return
endif
let job = s:jobs[a:name]
2014-10-09 12:55:36 +02:00
if v:job_data[1] == 'exit'
let job.running = 0
if s:lastline(job.result) ==# 'Error'
let job.error = 1
let job.result = substitute(job.result, "Error[\r\n]$", '', '')
endif
call s:reap(a:name)
2014-10-09 12:55:36 +02:00
call s:tick()
else
2014-10-27 16:22:09 +01:00
let job.result .= s:to_s(v:job_data[2])
2014-10-09 12:55:36 +02:00
" To reduce the number of buffer updates
let job.tick = get(job, 'tick', -1) + 1
if job.tick % len(s:jobs) == 0
call s:log(job.new ? '+' : '*', a:name, job.result)
2014-10-09 12:55:36 +02:00
endif
endif
endfunction
function! s:spawn(name, cmd, opts)
let job = { 'running': 1, 'new': get(a:opts, 'new', 0),
\ 'error': 0, 'result': '' }
let s:jobs[a:name] = job
if s:nvim
2014-10-09 12:55:36 +02:00
let x = jobstart(a:name, 'sh', ['-c',
\ (has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd)
\ . ' || echo Error'])
2014-10-09 12:55:36 +02:00
if x > 0
let job.jobid = x
augroup PlugJobControl
execute 'autocmd JobActivity' a:name printf('call s:job_handler(%s)', string(a:name))
2014-10-09 12:55:36 +02:00
augroup END
else
2014-10-09 12:55:36 +02:00
let job.running = 0
let job.error = 1
let job.result = x < 0 ? 'sh is not executable' :
\ 'Invalid arguments (or job table is full)'
2013-09-10 16:58:41 +02:00
endif
2014-10-09 12:55:36 +02:00
else
let params = has_key(a:opts, 'dir') ? [a:cmd, a:opts.dir] : [a:cmd]
let job.result = call('s:system', params)
let job.error = v:shell_error != 0
2014-10-10 08:48:17 +02:00
let job.running = 0
2014-10-09 12:55:36 +02:00
endif
endfunction
function! s:reap(name)
if s:nvim
2014-10-09 12:55:36 +02:00
silent! execute 'autocmd! PlugJobControl JobActivity' a:name
endif
let job = s:jobs[a:name]
if job.error
call add(s:update.errors, a:name)
elseif get(job, 'new', 0)
let s:update.new[a:name] = 1
endif
let s:update.bar .= job.error ? 'x' : '='
call s:log(job.error ? 'x' : '-', a:name, job.result)
2014-10-09 12:55:36 +02:00
call s:bar()
call remove(s:jobs, a:name)
endfunction
function! s:bar()
if s:switch_in()
let total = len(s:update.all)
call setline(1, (s:update.pull ? 'Updating' : 'Installing').
\ ' plugins ('.len(s:update.bar).'/'.total.')')
call s:progress_bar(2, s:update.bar, total)
call s:switch_out()
endif
endfunction
function! s:logpos(name)
for i in range(1, line('$'))
if getline(i) =~# '^[-+x*] '.a:name.':'
return i
endif
endfor
2014-10-09 12:55:36 +02:00
return 0
endfunction
function! s:log(bullet, name, lines)
2014-10-09 12:55:36 +02:00
if s:switch_in()
let pos = s:logpos(a:name)
if pos > 0
execute pos 'd _'
if pos > winheight('.')
let pos = 4
endif
else
let pos = 4
endif
call append(pos - 1, s:format_message(a:bullet, a:name, a:lines))
2014-10-09 12:55:36 +02:00
call s:switch_out()
endif
endfunction
function! s:update_vim()
let s:jobs = {}
2014-10-09 12:55:36 +02:00
call s:bar()
call s:tick()
2013-09-10 16:58:41 +02:00
endfunction
2014-10-09 12:55:36 +02:00
function! s:tick()
let pull = s:update.pull
let prog = s:progress_opt(s:nvim)
while 1 " Without TCO, Vim stack is bound to explode
2014-10-09 12:55:36 +02:00
if empty(s:update.todo)
if empty(s:jobs) && !s:update.fin
let s:update.fin = 1
call s:update_finish()
endif
return
endif
let name = keys(s:update.todo)[0]
let spec = remove(s:update.todo, name)
let new = !isdirectory(spec.dir)
call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...')
2014-10-09 12:55:36 +02:00
redraw
2015-02-18 08:47:32 +01:00
let checkout = s:shellesc(has_key(spec, 'tag') ? spec.tag : spec.branch)
let merge = s:shellesc(has_key(spec, 'tag') ? spec.tag : 'origin/'.spec.branch)
if !new
2014-10-09 12:55:36 +02:00
let [valid, msg] = s:git_valid(spec, 0)
if valid
if pull
call s:spawn(name,
2015-02-18 08:47:32 +01:00
\ printf('(git fetch %s 2>&1 && git checkout -q %s 2>&1 && git merge --ff-only %s 2>&1 && git submodule update --init --recursive 2>&1)',
\ prog, checkout, merge), { 'dir': spec.dir })
2014-10-09 12:55:36 +02:00
else
let s:jobs[name] = { 'running': 0, 'result': 'Already installed', 'error': 0 }
endif
else
let s:jobs[name] = { 'running': 0, 'result': msg, 'error': 1 }
endif
else
call s:spawn(name,
\ printf('git clone %s --recursive %s -b %s %s 2>&1',
\ prog,
2014-10-09 12:55:36 +02:00
\ s:shellesc(spec.uri),
2015-02-18 08:47:32 +01:00
\ checkout,
2014-10-09 12:55:36 +02:00
\ s:shellesc(s:trim(spec.dir))), { 'new': 1 })
endif
if !s:jobs[name].running
call s:reap(name)
endif
2014-10-10 03:39:49 +02:00
if len(s:jobs) >= s:update.threads
break
endif
2014-10-09 12:55:36 +02:00
endwhile
endfunction
function! s:update_python()
python << EOF
""" Due to use of signals this function is POSIX only. """
import datetime
import functools
import os
import Queue
import random
import re
import shutil
import signal
import subprocess
import tempfile
import threading as thr
import time
import traceback
import vim
G_PULL = vim.eval('s:update.pull') == '1'
G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1
G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)'))
G_PROGRESS = vim.eval('s:progress_opt(1)')
G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads'))
G_STOP = thr.Event()
class CmdTimedOut(Exception):
pass
class CmdFailed(Exception):
pass
class InvalidURI(Exception):
pass
class Action(object):
INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-']
class GLog(object):
ON = None
LOGDIR = None
@classmethod
def write(cls, msg):
if cls.ON is None:
cls.ON = int(vim.eval('get(g:, "plug_log_on", 0)'))
cls.LOGDIR = os.path.expanduser(vim.eval('get(g:, "plug_logs", "~/plug_logs")'))
if cls.ON:
if not os.path.exists(cls.LOGDIR):
os.makedirs(cls.LOGDIR)
cls._write(msg)
@classmethod
def _write(cls, msg):
name = thr.current_thread().name
fname = cls.LOGDIR + os.path.sep + name
with open(fname, 'ab') as flog:
ltime = datetime.datetime.now().strftime("%H:%M:%S.%f")
msg = '[{},{}] {}{}'.format(name, ltime, msg, '\n')
flog.write(msg)
class Buffer(object):
def __init__(self, lock, num_plugs):
self.bar = ''
self.event = 'Updating' if vim.eval('s:update.pull') == '1' else 'Installing'
self.is_win = vim.eval('s:is_win') == '1'
self.lock = lock
self.maxy = int(vim.eval('winheight(".")'))
self.num_plugs = num_plugs
def _where(self, name):
""" Find first line with name in current buffer. Return line num. """
found, lnum = False, 0
matcher = re.compile('^[-+x*] {}:'.format(name))
for line in vim.current.buffer:
if matcher.search(line) is not None:
found = True
break
lnum += 1
if not found:
lnum = -1
return lnum
def header(self):
curbuf = vim.current.buffer
curbuf[0] = self.event + ' plugins ({}/{})'.format(len(self.bar), self.num_plugs)
num_spaces = self.num_plugs - len(self.bar)
curbuf[1] = '[{}{}]'.format(self.bar, num_spaces * ' ')
vim.command('normal! 2G')
if not self.is_win:
vim.command('redraw')
def write(self, *args, **kwargs):
with self.lock:
self._write(*args, **kwargs)
def _write(self, action, name, lines):
first, rest = lines[0], lines[1:]
msg = ['{} {}{}{}'.format(action, name, ': ' if first else '', first)]
padded_rest = [' ' + line for line in rest]
msg.extend(padded_rest)
try:
if action == Action.ERROR:
self.bar += 'x'
vim.command("call add(s:update.errors, '{}')".format(name))
elif action == Action.DONE:
self.bar += '='
curbuf = vim.current.buffer
lnum = self._where(name)
if lnum != -1: # Found matching line num
del curbuf[lnum]
if lnum > self.maxy and action in {Action.INSTALL, Action.UPDATE}:
lnum = 3
else:
lnum = 3
curbuf.append(msg, lnum)
self.header()
except vim.error:
GLog.write('Buffer Update FAILED.')
class Command(object):
def __init__(self, cmd, cmd_dir=None, timeout=60, ntries=3, cb=None, clean=None):
self.cmd = cmd
self.cmd_dir = cmd_dir
self.timeout = timeout
self.ntries = ntries
self.callback = cb if cb else (lambda msg: None)
self.clean = clean
def attempt_cmd(self):
""" Tries to run the command, returns result if no exceptions. """
attempt = 0
finished = False
limit = self.timeout
while not finished:
try:
attempt += 1
result = self.timeout_cmd()
finished = True
except CmdTimedOut:
if attempt != self.ntries:
for count in range(3, 0, -1):
if G_STOP.is_set():
raise KeyboardInterrupt
msg = 'Timeout. Will retry in {} second{} ...'.format(
count, 's' if count != 1 else '')
self.callback([msg])
time.sleep(1)
self.timeout += limit
self.callback(['Retrying ...'])
else:
raise
return result
def timeout_cmd(self):
""" Execute a cmd & poll for callback. Returns list of output.
Raises CmdFailed -> return code for Popen isn't 0
Raises CmdTimedOut -> command exceeded timeout without new output
"""
proc = None
first_line = True
try:
tfile = tempfile.NamedTemporaryFile()
proc = subprocess.Popen(self.cmd, cwd=self.cmd_dir, stdout=tfile,
stderr=subprocess.STDOUT, shell=True, preexec_fn=os.setsid)
while proc.poll() is None:
# Yield this thread
time.sleep(0.2)
if G_STOP.is_set():
raise KeyboardInterrupt
if first_line or random.random() < G_LOG_PROB:
first_line = False
line = nonblock_read(tfile.name)
if line:
self.callback([line])
time_diff = time.time() - os.path.getmtime(tfile.name)
if time_diff > self.timeout:
raise CmdTimedOut(['Timeout!'])
tfile.seek(0)
result = [line.rstrip() for line in tfile]
if proc.returncode != 0:
msg = ['']
msg.extend(result)
raise CmdFailed(msg)
except:
if proc and proc.poll() is None:
os.killpg(proc.pid, signal.SIGTERM)
if self.clean:
self.clean()
raise
return result
class Plugin(object):
def __init__(self, name, args, buf, lock):
self.name = name
self.args = args
self.buf = buf
self.lock = lock
tag = args.get('tag', 0)
self.checkout = esc(tag if tag else args['branch'])
self.merge = esc(tag if tag else 'origin/' + args['branch'])
def manage(self):
try:
if os.path.exists(self.args['dir']):
self.update()
else:
self.install()
with self.lock:
vim.command("let s:update.new['{}'] = 1".format(self.name))
except (CmdTimedOut, CmdFailed, InvalidURI) as exc:
self.write(Action.ERROR, self.name, exc.message)
except KeyboardInterrupt:
G_STOP.set()
self.write(Action.ERROR, self.name, ['Interrupted!'])
except:
# Any exception except those above print stack trace
msg = 'Trace:\n{}'.format(traceback.format_exc().rstrip())
self.write(Action.ERROR, self.name, msg.split('\n'))
raise
def install(self):
target = self.args['dir']
def clean(target):
def _clean():
try:
shutil.rmtree(target)
except OSError:
pass
return _clean
self.write(Action.INSTALL, self.name, ['Installing ...'])
callback = functools.partial(self.buf.write, Action.INSTALL, self.name)
cmd = 'git clone {} --recursive {} -b {} {} 2>&1'.format(
G_PROGRESS, self.args['uri'], self.checkout, esc(target))
com = Command(cmd, None, G_TIMEOUT, G_RETRIES, callback, clean(target))
result = com.attempt_cmd()
self.write(Action.DONE, self.name, result[-1:])
def update(self):
match = re.compile(r'git::?@')
actual_uri = re.sub(match, '', self.repo_uri())
expect_uri = re.sub(match, '', self.args['uri'])
if actual_uri != expect_uri:
msg = ['',
'Invalid URI: {}'.format(actual_uri),
'Expected {}'.format(expect_uri),
'PlugClean required.']
raise InvalidURI(msg)
if G_PULL:
self.write(Action.UPDATE, self.name, ['Updating ...'])
callback = functools.partial(self.buf.write, Action.UPDATE, self.name)
cmds = ['git fetch {}'.format(G_PROGRESS),
'git checkout -q {}'.format(self.checkout),
'git merge --ff-only {}'.format(self.merge),
'git submodule update --init --recursive']
cmd = ' 2>&1 && '.join(cmds)
GLog.write(cmd)
com = Command(cmd, self.args['dir'], G_TIMEOUT, G_RETRIES, callback)
result = com.attempt_cmd()
GLog.write(result)
self.write(Action.DONE, self.name, result[-1:])
else:
self.write(Action.DONE, self.name, ['Already installed'])
def repo_uri(self):
cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url'
command = Command(cmd, self.args['dir'], G_TIMEOUT, G_RETRIES)
result = command.attempt_cmd()
return result[-1]
def write(self, action, name, msg):
GLog.write('{} {}: {}'.format(action, name, '\n'.join(msg)))
self.buf.write(action, name, msg)
class PlugThread(thr.Thread):
def __init__(self, tname, args):
super(PlugThread, self).__init__()
self.tname = tname
self.args = args
def run(self):
thr.current_thread().name = self.tname
work_q, lock, buf = self.args
try:
while not G_STOP.is_set():
name, args = work_q.get_nowait()
GLog.write('{}: Dir {}'.format(name, args['dir']))
plug = Plugin(name, args, buf, lock)
plug.manage()
work_q.task_done()
except Queue.Empty:
GLog.write('Queue now empty.')
class RefreshThread(thr.Thread):
def __init__(self, lock):
super(RefreshThread, self).__init__()
self.lock = lock
self.running = True
def run(self):
while self.running:
with self.lock:
vim.command('noautocmd normal! a')
time.sleep(0.2)
def stop(self):
self.running = False
def esc(name):
return '"' + name.replace('"', '\"') + '"'
def nonblock_read(fname):
""" Read a file with nonblock flag. Return the last line. """
fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
buf = os.read(fread, 100000)
os.close(fread)
line = buf.rstrip('\r\n')
left = max(line.rfind('\r'), line.rfind('\n'))
if left != -1:
left += 1
line = line[left:]
return line
def main():
thr.current_thread().name = 'main'
GLog.write('')
if GLog.ON and os.path.exists(GLog.LOGDIR):
shutil.rmtree(GLog.LOGDIR)
threads = []
nthreads = int(vim.eval('s:update.threads'))
plugs = vim.eval('s:update.todo')
mac_gui = vim.eval('s:mac_gui') == '1'
is_win = vim.eval('s:is_win') == '1'
GLog.write('Plugs: {}'.format(plugs))
GLog.write('PULL: {}, WIN: {}, MAC: {}'.format(G_PULL, is_win, mac_gui))
GLog.write('Num Threads: {}'.format(nthreads))
lock = thr.Lock()
buf = Buffer(lock, len(plugs))
work_q = Queue.Queue()
for work in plugs.items():
work_q.put(work)
GLog.write('Starting Threads')
for num in range(nthreads):
tname = 'PlugT-{0:02}'.format(num)
thread = PlugThread(tname, (work_q, lock, buf))
thread.start()
threads.append(thread)
if mac_gui:
rthread = RefreshThread(lock)
rthread.start()
GLog.write('Joining Live Threads')
for thread in threads:
thread.join()
if mac_gui:
rthread.stop()
rthread.join()
GLog.write('Cleanly Exited Main')
main()
EOF
endfunction
2014-10-09 12:55:36 +02:00
function! s:update_ruby()
2013-09-10 16:58:41 +02:00
ruby << EOF
module PlugStream
SEP = ["\r", "\n", nil]
def get_line
buffer = ''
loop do
char = readchar rescue return
if SEP.include? char.chr
buffer << $/
break
else
buffer << char
end
end
buffer
end
end unless defined?(PlugStream)
def esc arg
%["#{arg.gsub('"', '\"')}"]
end
def killall pid
pids = [pid]
unless `which pgrep 2> /dev/null`.empty?
children = pids
until children.empty?
children = children.map { |pid|
`pgrep -P #{pid}`.lines.map { |l| l.chomp }
}.flatten
pids += children
end
end
pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil }
end
require 'thread'
require 'fileutils'
require 'timeout'
running = true
iswin = VIM::evaluate('s:is_win').to_i == 1
2014-10-09 12:55:36 +02:00
pull = VIM::evaluate('s:update.pull').to_i == 1
2013-09-10 16:58:41 +02:00
base = VIM::evaluate('g:plug_home')
2014-10-09 12:55:36 +02:00
all = VIM::evaluate('s:update.todo')
limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
tries = VIM::evaluate('get(g:, "plug_retries", 2)') + 1
2014-10-09 12:55:36 +02:00
nthr = VIM::evaluate('s:update.threads').to_i
maxy = VIM::evaluate('winheight(".")').to_i
cd = iswin ? 'cd /d' : 'cd'
2014-10-09 12:55:36 +02:00
tot = VIM::evaluate('len(s:update.todo)') || 0
2013-12-04 15:41:53 +01:00
bar = ''
2013-09-10 16:58:41 +02:00
skip = 'Already installed'
2013-09-11 05:06:57 +02:00
mtx = Mutex.new
take1 = proc { mtx.synchronize { running && all.shift } }
logh = proc {
cnt = bar.length
$curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
2013-12-04 15:41:53 +01:00
$curbuf[2] = '[' + bar.ljust(tot) + ']'
VIM::command('normal! 2G')
VIM::command('redraw') unless iswin
}
where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
log = proc { |name, result, type|
mtx.synchronize do
ing = ![true, false].include?(type)
bar += type ? '=' : 'x' unless ing
b = case type
when :install then '+' when :update then '*'
when true, nil then '-' else
2014-10-09 12:55:36 +02:00
VIM::command("call add(s:update.errors, '#{name}')")
'x'
end
2014-01-06 05:04:38 +01:00
result =
if type || type.nil?
["#{b} #{name}: #{result.lines.to_a.last}"]
2014-01-06 05:04:38 +01:00
elsif result =~ /^Interrupted|^Timeout/
["#{b} #{name}: #{result}"]
2014-01-06 05:04:38 +01:00
else
["#{b} #{name}"] + result.lines.map { |l| " " << l }
2014-01-06 05:04:38 +01:00
end
if lnum = where.call(name)
$curbuf.delete lnum
lnum = 4 if ing && lnum > maxy
end
2014-01-06 05:04:38 +01:00
result.each_with_index do |line, offset|
$curbuf.append((lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp)
2014-01-06 05:04:38 +01:00
end
logh.call
end
}
bt = proc { |cmd, name, type, cleanup|
tried = timeout = 0
begin
tried += 1
timeout += limit
fd = nil
data = ''
if iswin
Timeout::timeout(timeout) do
tmp = VIM::evaluate('tempname()')
system("#{cmd} > #{tmp}")
data = File.read(tmp).chomp
File.unlink tmp rescue nil
end
else
fd = IO.popen(cmd).extend(PlugStream)
first_line = true
log_prob = 1.0 / nthr
while line = Timeout::timeout(timeout) { fd.get_line }
data << line
log.call name, line.chomp, type if name && (first_line || rand < log_prob)
first_line = false
end
fd.close
end
[$? == 0, data.chomp]
rescue Timeout::Error, Interrupt => e
if fd && !fd.closed?
killall fd.pid
fd.close
end
cleanup.call if cleanup
2014-07-21 16:09:16 +02:00
if e.is_a?(Timeout::Error) && tried < tries
3.downto(1) do |countdown|
s = countdown > 1 ? 's' : ''
log.call name, "Timeout. Will retry in #{countdown} second#{s} ...", type
sleep 1
end
log.call name, 'Retrying ...', type
retry
end
[false, e.is_a?(Interrupt) ? "Interrupted!" : "Timeout!"]
end
}
main = Thread.current
threads = []
watcher = Thread.new {
while VIM::evaluate('getchar(1)')
sleep 0.1
end
mtx.synchronize do
running = false
threads.each { |t| t.raise Interrupt }
end
threads.each { |t| t.join rescue nil }
main.kill
}
refresh = Thread.new {
while true
mtx.synchronize do
break unless running
VIM::command('noautocmd normal! a')
end
sleep 0.2
end
} if VIM::evaluate('s:mac_gui') == 1
progress = VIM::evaluate('s:progress_opt(1)')
2014-10-09 12:55:36 +02:00
nthr.times do
mtx.synchronize do
threads << Thread.new {
while pair = take1.call
name = pair.first
2015-02-18 08:47:32 +01:00
dir, uri, branch, tag = pair.last.values_at *%w[dir uri branch tag]
checkout = esc(tag ? tag : branch)
merge = esc(tag ? tag : "origin/#{branch}")
subm = "git submodule update --init --recursive 2>&1"
exists = File.directory? dir
ok, result =
if exists
dir = esc dir
ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil, nil
current_uri = data.lines.to_a.last
if !ret
if data =~ /^Interrupted|^Timeout/
[false, data]
2014-01-06 05:04:38 +01:00
else
[false, [data.chomp, "PlugClean required."].join($/)]
end
elsif current_uri.sub(/git::?@/, '') != uri.sub(/git::?@/, '')
[false, ["Invalid URI: #{current_uri}",
"Expected: #{uri}",
"PlugClean required."].join($/)]
2013-09-17 05:35:10 +02:00
else
if pull
log.call name, 'Updating ...', :update
2015-02-18 08:47:32 +01:00
bt.call "#{cd} #{dir} && (git fetch #{progress} 2>&1 && git checkout -q #{checkout} 2>&1 && git merge --ff-only #{merge} 2>&1 && #{subm})", name, :update, nil
else
[true, skip]
end
2013-09-17 05:35:10 +02:00
end
else
d = esc dir.sub(%r{[\\/]+$}, '')
log.call name, 'Installing ...', :install
2015-02-18 08:47:32 +01:00
bt.call "git clone #{progress} --recursive #{uri} -b #{checkout} #{d} 2>&1", name, :install, proc {
FileUtils.rm_rf dir
}
end
2014-10-09 12:55:36 +02:00
mtx.synchronize { VIM::command("let s:update.new['#{name}'] = 1") } if !exists && ok
log.call name, result, ok
end
} if running
end
end
threads.each { |t| t.join rescue nil }
logh.call
refresh.kill if refresh
watcher.kill
2013-09-10 16:58:41 +02:00
EOF
endfunction
function! s:shellesc(arg)
return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
endfunction
2013-09-10 16:58:41 +02:00
function! s:glob_dir(path)
2014-10-10 03:10:52 +02:00
return map(filter(s:lines(globpath(a:path, '**')), 'isdirectory(v:val)'), 's:dirpath(v:val)')
2013-09-10 16:58:41 +02:00
endfunction
2013-12-04 15:41:53 +01:00
function! s:progress_bar(line, bar, total)
call setline(a:line, '[' . s:lpad(a:bar, a:total) . ']')
2013-09-17 15:57:13 +02:00
endfunction
function! s:compare_git_uri(a, b)
let a = substitute(a:a, 'git:\{1,2}@', '', '')
let b = substitute(a:b, 'git:\{1,2}@', '', '')
return a ==# b
endfunction
function! s:format_message(bullet, name, message)
if a:bullet != 'x'
return [printf('%s %s: %s', a:bullet, a:name, s:lastline(a:message))]
2014-01-06 05:04:38 +01:00
else
let lines = map(s:lines(a:message), '" ".v:val')
2014-01-06 05:04:38 +01:00
return extend([printf('x %s:', a:name)], lines)
endif
endfunction
2014-10-09 12:55:36 +02:00
function! s:with_cd(cmd, dir)
2015-01-29 11:08:03 +01:00
return (s:is_win ? 'cd /d ' : 'cd ').s:esc(a:dir).' && '.a:cmd
2014-10-09 12:55:36 +02:00
endfunction
2014-10-01 04:37:58 +02:00
function! s:system(cmd, ...)
try
let sh = &shell
if !s:is_win
set shell=sh
endif
let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd
return system(s:is_win ? '('.cmd.')' : cmd)
finally
let &shell = sh
endtry
2014-03-18 16:43:37 +01:00
endfunction
2014-10-01 04:37:58 +02:00
function! s:system_chomp(...)
let ret = call('s:system', a:000)
return v:shell_error ? '' : substitute(ret, '\n$', '', '')
endfunction
2014-10-01 04:37:58 +02:00
function! s:git_valid(spec, check_branch)
let ret = 1
let msg = 'OK'
2013-09-17 15:57:13 +02:00
if isdirectory(a:spec.dir)
2014-10-10 03:10:52 +02:00
let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url', a:spec.dir))
2014-01-06 05:04:38 +01:00
let remote = result[-1]
if v:shell_error
let msg = join([remote, 'PlugClean required.'], "\n")
let ret = 0
2014-01-06 05:04:38 +01:00
elseif !s:compare_git_uri(remote, a:spec.uri)
let msg = join(['Invalid URI: '.remote,
\ 'Expected: '.a:spec.uri,
\ 'PlugClean required.'], "\n")
2014-01-06 05:04:38 +01:00
let ret = 0
elseif a:check_branch
let branch = result[0]
2015-02-18 08:47:32 +01:00
" Check tag
if has_key(a:spec, 'tag')
2014-10-01 04:37:58 +02:00
let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir)
2015-02-18 08:47:32 +01:00
if a:spec.tag !=# tag
let msg = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.',
\ (empty(tag) ? 'N/A' : tag), a:spec.tag)
let ret = 0
endif
2015-02-18 08:47:32 +01:00
" Check branch
elseif a:spec.branch !=# branch
let msg = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.',
\ branch, a:spec.branch)
let ret = 0
endif
endif
2013-09-17 15:57:13 +02:00
else
let msg = 'Not found'
2013-09-17 15:57:13 +02:00
let ret = 0
endif
return [ret, msg]
2013-09-17 05:35:10 +02:00
endfunction
2013-09-17 15:57:13 +02:00
function! s:clean(force)
2013-09-10 16:58:41 +02:00
call s:prepare()
2013-09-18 05:19:19 +02:00
call append(0, 'Searching for unused plugins in '.g:plug_home)
2013-09-17 15:57:13 +02:00
call append(1, '')
2013-09-10 16:58:41 +02:00
2013-09-17 05:35:10 +02:00
" List of valid directories
2013-09-17 15:57:13 +02:00
let dirs = []
let [cnt, total] = [0, len(g:plugs)]
for [name, spec] in items(g:plugs)
2014-10-01 04:37:58 +02:00
if !s:is_managed(name) || s:git_valid(spec, 0)[0]
2013-09-17 15:57:13 +02:00
call add(dirs, spec.dir)
endif
let cnt += 1
2013-12-04 15:41:53 +01:00
call s:progress_bar(2, repeat('=', cnt), total)
normal! 2G
2013-09-17 15:57:13 +02:00
redraw
endfor
2013-09-17 05:35:10 +02:00
2013-09-10 16:58:41 +02:00
let allowed = {}
2013-09-26 08:08:52 +02:00
for dir in dirs
let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1
2013-09-10 16:58:41 +02:00
let allowed[dir] = 1
2013-09-26 08:08:52 +02:00
for child in s:glob_dir(dir)
let allowed[child] = 1
endfor
2013-09-10 16:58:41 +02:00
endfor
let todo = []
let found = sort(s:glob_dir(g:plug_home))
while !empty(found)
let f = remove(found, 0)
if !has_key(allowed, f) && isdirectory(f)
call add(todo, f)
call append(line('$'), '- ' . f)
let found = filter(found, 'stridx(v:val, f) != 0')
end
endwhile
normal! G
redraw
if empty(todo)
call append(line('$'), 'Already clean.')
else
call inputsave()
let yes = a:force || (input('Proceed? (y/N) ') =~? '^y')
2013-09-10 16:58:41 +02:00
call inputrestore()
2013-09-18 05:23:43 +02:00
if yes
2013-09-10 16:58:41 +02:00
for dir in todo
if isdirectory(dir)
call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir))
2013-09-10 16:58:41 +02:00
endif
endfor
call append(line('$'), 'Removed.')
else
call append(line('$'), 'Cancelled.')
endif
endif
normal! G
endfunction
function! s:upgrade()
let new = s:me . '.new'
2014-10-10 08:48:17 +02:00
echo 'Downloading '. s:plug_src
redraw
try
if executable('curl')
let output = s:system(printf('curl -fLo %s %s', s:shellesc(new), s:plug_src))
if v:shell_error
2014-10-10 03:10:52 +02:00
throw get(s:lines(output), -1, v:shell_error)
endif
elseif s:ruby
call s:upgrade_using_ruby(new)
elseif s:py2
call s:upgrade_using_python(new)
else
return s:err('Missing: curl executable, ruby support or python support')
endif
catch
return s:err('Error upgrading vim-plug: '. v:exception)
endtry
if readfile(s:me) ==# readfile(new)
2015-01-26 10:46:20 +01:00
echo 'vim-plug is already up-to-date'
silent! call delete(new)
return 0
2013-09-10 16:58:41 +02:00
else
call rename(s:me, s:me . '.old')
call rename(new, s:me)
unlet g:loaded_plug
2015-01-26 10:46:20 +01:00
echo 'vim-plug has been upgraded'
return 1
2013-09-10 16:58:41 +02:00
endif
endfunction
function! s:upgrade_using_ruby(new)
ruby << EOF
require 'open-uri'
File.open(VIM::evaluate('a:new'), 'w') do |f|
f << open(VIM::evaluate('s:plug_src')).read
end
EOF
endfunction
function! s:upgrade_using_python(new)
python << EOF
import urllib
import vim
psrc, dest = vim.eval('s:plug_src'), vim.eval('a:new')
urllib.urlretrieve(psrc, dest)
EOF
endfunction
function! s:upgrade_specs()
for spec in values(g:plugs)
let spec.frozen = get(spec, 'frozen', 0)
endfor
endfunction
2013-09-23 10:33:36 +02:00
function! s:status()
call s:prepare()
call append(0, 'Checking plugins')
call append(1, '')
2013-09-23 10:33:36 +02:00
let ecnt = 0
let unloaded = 0
let [cnt, total] = [0, len(g:plugs)]
2013-09-23 10:33:36 +02:00
for [name, spec] in items(g:plugs)
if has_key(spec, 'uri')
if isdirectory(spec.dir)
2014-10-01 04:37:58 +02:00
let [valid, msg] = s:git_valid(spec, 1)
else
let [valid, msg] = [0, 'Not found. Try PlugInstall.']
endif
2013-09-23 10:33:36 +02:00
else
if isdirectory(spec.dir)
let [valid, msg] = [1, 'OK']
else
let [valid, msg] = [0, 'Not found.']
endif
2013-09-23 10:33:36 +02:00
endif
let cnt += 1
let ecnt += !valid
" `s:loaded` entry can be missing if PlugUpgraded
if valid && get(s:loaded, name, -1) == 0
let unloaded = 1
let msg .= ' (not loaded)'
endif
call s:progress_bar(2, repeat('=', cnt), total)
call append(3, s:format_message(valid ? '-' : 'x', name, msg))
normal! 2G
2013-09-23 10:33:36 +02:00
redraw
endfor
call setline(1, 'Finished. '.ecnt.' error(s).')
normal! gg
setlocal nomodifiable
if unloaded
echo "Press 'L' on each line to load plugin, or 'U' to update"
nnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
xnoremap <silent> <buffer> L :call <SID>status_load(line('.'))<cr>
end
endfunction
function! s:extract_name(str, prefix, suffix)
2014-09-15 14:03:37 +02:00
return matchstr(a:str, '^'.a:prefix.' \zs[^:]\+\ze:.*'.a:suffix.'$')
endfunction
function! s:status_load(lnum)
let line = getline(a:lnum)
let name = s:extract_name(line, '-', '(not loaded)')
if !empty(name)
call plug#load(name)
setlocal modifiable
call setline(a:lnum, substitute(line, ' (not loaded)$', '', ''))
setlocal nomodifiable
endif
2013-09-23 10:33:36 +02:00
endfunction
function! s:status_update() range
2014-09-15 14:03:37 +02:00
let lines = getline(a:firstline, a:lastline)
let names = filter(map(lines, 's:extract_name(v:val, "[x-]", "")'), '!empty(v:val)')
if !empty(names)
echo
execute 'PlugUpdate' join(names)
endif
endfunction
function! s:is_preview_window_open()
silent! wincmd P
if &previewwindow
wincmd p
return 1
endif
return 0
endfunction
function! s:find_name(lnum)
for lnum in reverse(range(1, a:lnum))
let line = getline(lnum)
if empty(line)
return ''
endif
2014-10-10 10:34:39 +02:00
let name = s:extract_name(line, '-', '')
if !empty(name)
return name
endif
endfor
return ''
endfunction
function! s:preview_commit()
if b:plug_preview < 0
let b:plug_preview = !s:is_preview_window_open()
endif
let sha = matchstr(getline('.'), '\(^ \)\@<=[0-9a-z]\{7}')
if empty(sha)
return
endif
let name = s:find_name(line('.'))
if empty(name) || !has_key(g:plugs, name) || !isdirectory(g:plugs[name].dir)
return
endif
execute 'pedit' sha
wincmd P
setlocal filetype=git buftype=nofile nobuflisted
2014-10-01 04:37:58 +02:00
execute 'silent read !cd' s:esc(g:plugs[name].dir) '&& git show' sha
normal! gg"_dd
wincmd p
endfunction
function! s:section(flags)
2014-10-10 10:35:07 +02:00
call search('\(^[x-] \)\@<=[^:]\+:', a:flags)
endfunction
2014-04-08 14:43:49 +02:00
function! s:diff()
call s:prepare()
call append(0, 'Collecting updated changes ...')
normal! gg
redraw
2014-04-08 14:43:49 +02:00
2014-04-08 17:40:19 +02:00
let cnt = 0
2014-04-08 14:43:49 +02:00
for [k, v] in items(g:plugs)
if !isdirectory(v.dir) || !s:is_managed(k)
2014-04-08 14:43:49 +02:00
continue
endif
2014-10-01 04:37:58 +02:00
let diff = s:system_chomp('git log --pretty=format:"%h %s (%cr)" "HEAD...HEAD@{1}"', v.dir)
if !empty(diff)
2014-04-08 14:43:49 +02:00
call append(1, '')
call append(2, '- '.k.':')
2014-10-10 03:10:52 +02:00
call append(3, map(s:lines(diff), '" ". v:val'))
2014-04-08 17:40:19 +02:00
let cnt += 1
2014-04-08 14:43:49 +02:00
normal! gg
redraw
endif
endfor
call setline(1, cnt == 0 ? 'No updates.' : 'Last update:')
nnoremap <silent> <buffer> <cr> :silent! call <SID>preview_commit()<cr>
2015-02-17 06:09:07 +01:00
nnoremap <silent> <buffer> o :silent! call <SID>preview_commit()<cr>
nnoremap <silent> <buffer> X :call <SID>revert()<cr>
2014-04-08 17:40:19 +02:00
normal! gg
setlocal nomodifiable
if cnt > 0
echo "Press 'X' on each block to revert the update"
endif
endfunction
function! s:revert()
let name = s:find_name(line('.'))
if empty(name) || !has_key(g:plugs, name) ||
\ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y'
return
endif
2014-10-01 04:37:58 +02:00
call s:system('git reset --hard HEAD@{1} && git checkout '.s:esc(g:plugs[name].branch), g:plugs[name].dir)
setlocal modifiable
normal! "_dap
setlocal nomodifiable
echo 'Reverted.'
2014-04-08 14:43:49 +02:00
endfunction
function! s:snapshot(...) abort
let home = get(s:, 'plug_home_org', g:plug_home)
let [type, var, header] = s:is_win ?
\ ['dosbatch', '%PLUG_HOME%',
\ ['@echo off', ':: Generated by vim-plug', ':: '.strftime("%c"), '',
\ ':: Make sure to PlugUpdate first', '', 'set PLUG_HOME='.s:esc(home)]] :
\ ['sh', '$PLUG_HOME',
\ ['#!/bin/sh', '# Generated by vim-plug', '# '.strftime("%c"), '',
\ 'vim +PlugUpdate +qa', '', 'PLUG_HOME='.s:esc(home)]]
call s:prepare()
execute 'setf' type
call append(0, header)
call append('$', '')
1
redraw
let dirs = sort(map(values(filter(copy(g:plugs),
\'has_key(v:val, "uri") && isdirectory(v:val.dir)')), 'v:val.dir'))
let anchor = line('$') - 1
for dir in reverse(dirs)
2014-10-01 04:37:58 +02:00
let sha = s:system_chomp('git rev-parse --short HEAD', dir)
if !empty(sha)
call append(anchor, printf('cd %s && git reset --hard %s',
\ substitute(dir, '^'.g:plug_home, var, ''), sha))
redraw
endif
endfor
if a:0 > 0
let fn = expand(a:1)
let fne = s:esc(fn)
call writefile(getline(1, '$'), fn)
if !s:is_win | call s:system('chmod +x ' . fne) | endif
echo 'Saved to '.a:1
silent execute 'e' fne
endif
endfunction
function! s:split_rtp()
return split(&rtp, '\\\@<!,')
endfunction
let s:first_rtp = s:escrtp(get(s:split_rtp(), 0, ''))
let s:last_rtp = s:escrtp(get(s:split_rtp(), -1, ''))
2014-07-16 13:26:42 +02:00
if exists('g:plugs')
let g:plugs_order = get(g:, 'plugs_order', keys(g:plugs))
call s:upgrade_specs()
call s:define_commands()
endif
let &cpo = s:cpo_save
unlet s:cpo_save