f1433663ee
So it can be reused. Fixes #441
535 lines
17 KiB
VimL
535 lines
17 KiB
VimL
" ---------------------------------------------------------------------------
|
|
" Try to clone all new bundles given (or all bundles in g:bundles by default)
|
|
" to g:bundle_dir. If a:bang is 1 it will also update all plugins (git pull).
|
|
"
|
|
" bang -- 1 or 0
|
|
" ... -- any number of bundle specifications (separate arguments)
|
|
" ---------------------------------------------------------------------------
|
|
func! vundle#installer#new(bang, ...) abort
|
|
let bundles = (a:1 == '') ?
|
|
\ g:bundles :
|
|
\ map(copy(a:000), 'vundle#config#bundle(v:val, {})')
|
|
|
|
let names = vundle#scripts#bundle_names(map(copy(bundles), 'v:val.name_spec'))
|
|
call vundle#scripts#view('Installer',['" Installing plugins to '.expand(g:bundle_dir, 1)], names + ['Helptags'])
|
|
|
|
" This calls 'add' as a normal mode command. This is a buffer local mapping
|
|
" defined in vundle#scripts#view(). The mapping will call a buffer local
|
|
" command InstallPlugin which in turn will call vundle#installer#run() with
|
|
" vundle#installer#install().
|
|
call s:process(a:bang, (a:bang ? 'add!' : 'add'))
|
|
|
|
call vundle#config#require(bundles)
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Iterate over all lines in a Vundle window and execute the given command for
|
|
" every line. Used by the installation and cleaning functions.
|
|
"
|
|
" bang -- not used (FIXME)
|
|
" cmd -- the (normal mode) command to execute for every line as a string
|
|
" ---------------------------------------------------------------------------
|
|
func! s:process(bang, cmd)
|
|
let msg = ''
|
|
|
|
redraw
|
|
sleep 1m
|
|
|
|
let lines = (getline('.','$')[0:-2])
|
|
|
|
for line in lines
|
|
redraw
|
|
|
|
exec ':norm '.a:cmd
|
|
|
|
if 'error' == g:vundle_last_status
|
|
let msg = 'With errors; press l to view log'
|
|
endif
|
|
|
|
if 'updated' == g:vundle_last_status && empty(msg)
|
|
let msg = 'Plugins updated; press u to view changelog'
|
|
endif
|
|
|
|
" goto next one
|
|
exec ':+1'
|
|
|
|
setl nomodified
|
|
endfor
|
|
|
|
redraw
|
|
echo 'Done! '.msg
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Call another function in the different Vundle windows.
|
|
"
|
|
" func_name -- the function to call
|
|
" name -- the bundle name to call func_name for (string)
|
|
" ... -- the argument to be used when calling func_name (only the first
|
|
" optional argument will be used)
|
|
" return -- the status returned by the call to func_name
|
|
" ---------------------------------------------------------------------------
|
|
func! vundle#installer#run(func_name, name, ...) abort
|
|
let n = a:name
|
|
|
|
echo 'Processing '.n
|
|
call s:sign('active')
|
|
|
|
sleep 1m
|
|
|
|
let status = call(a:func_name, a:1)
|
|
|
|
call s:sign(status)
|
|
|
|
redraw
|
|
|
|
if 'new' == status
|
|
echo n.' installed'
|
|
elseif 'updated' == status
|
|
echo n.' updated'
|
|
elseif 'todate' == status
|
|
echo n.' already installed'
|
|
elseif 'deleted' == status
|
|
echo n.' deleted'
|
|
elseif 'helptags' == status
|
|
echo n.' regenerated'
|
|
elseif 'pinned' == status
|
|
echo n.' pinned'
|
|
elseif 'error' == status
|
|
echohl Error
|
|
echo 'Error processing '.n
|
|
echohl None
|
|
sleep 1
|
|
else
|
|
throw 'whoops, unknown status:'.status
|
|
endif
|
|
|
|
let g:vundle_last_status = status
|
|
|
|
return status
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Put a sign on the current line, indicating the status of the installation
|
|
" step.
|
|
"
|
|
" status -- string describing the status
|
|
" ---------------------------------------------------------------------------
|
|
func! s:sign(status)
|
|
if (!has('signs'))
|
|
return
|
|
endif
|
|
|
|
exe ":sign place ".line('.')." line=".line('.')." name=Vu_". a:status ." buffer=" . bufnr("%")
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Install a plugin, then add it to the runtimepath and source it.
|
|
"
|
|
" bang -- 1 or 0, passed directly to vundle#installer#install()
|
|
" name -- the name of a bundle (string)
|
|
" return -- the return value from vundle#installer#install()
|
|
" ---------------------------------------------------------------------------
|
|
func! vundle#installer#install_and_require(bang, name) abort
|
|
let result = vundle#installer#install(a:bang, a:name)
|
|
let b = vundle#config#bundle(a:name, {})
|
|
call vundle#installer#helptags([b])
|
|
call vundle#config#require([b])
|
|
return result
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Install or update a bundle given by its name.
|
|
"
|
|
" bang -- 1 or 0, passed directly to s:sync()
|
|
" name -- the name of a bundle (string)
|
|
" return -- the return value from s:sync()
|
|
" ---------------------------------------------------------------------------
|
|
func! vundle#installer#install(bang, name) abort
|
|
if !isdirectory(g:bundle_dir) | call mkdir(g:bundle_dir, 'p') | endif
|
|
|
|
let n = substitute(a:name,"['".'"]\+','','g')
|
|
let matched = filter(copy(g:bundles), 'v:val.name_spec == n')
|
|
|
|
if len(matched) > 0
|
|
let b = matched[0]
|
|
else
|
|
let b = vundle#config#init_bundle(a:name, {})
|
|
endif
|
|
|
|
return s:sync(a:bang, b)
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Call :helptags for all bundles in g:bundles.
|
|
"
|
|
" return -- 'error' if an error occurred, else return 'helptags'
|
|
" ---------------------------------------------------------------------------
|
|
func! vundle#installer#docs() abort
|
|
let error_count = vundle#installer#helptags(g:bundles)
|
|
if error_count > 0
|
|
return 'error'
|
|
endif
|
|
return 'helptags'
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Call :helptags for a list of bundles.
|
|
"
|
|
" bundles -- a list of bundle dictionaries for which :helptags should be
|
|
" called.
|
|
" return -- the number of directories where :helptags failed
|
|
" ---------------------------------------------------------------------------
|
|
func! vundle#installer#helptags(bundles) abort
|
|
let bundle_dirs = map(copy(a:bundles),'v:val.rtpath')
|
|
let help_dirs = filter(bundle_dirs, 's:has_doc(v:val)')
|
|
|
|
call s:log('')
|
|
call s:log('Helptags:')
|
|
|
|
let statuses = map(copy(help_dirs), 's:helptags(v:val)')
|
|
let errors = filter(statuses, 'v:val == 0')
|
|
|
|
call s:log('Helptags: '.len(help_dirs).' plugins processed')
|
|
|
|
return len(errors)
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" List all installed plugins.
|
|
" Corresponding documentation: vundle-plugins-list
|
|
"
|
|
" bang -- not used
|
|
" ---------------------------------------------------------------------------
|
|
func! vundle#installer#list(bang) abort
|
|
let bundles = vundle#scripts#bundle_names(map(copy(g:bundles), 'v:val.name_spec'))
|
|
call vundle#scripts#view('list', ['" My Plugins'], bundles)
|
|
redraw
|
|
echo len(g:bundles).' plugins configured'
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" List and remove all directories in the bundle directory which are not
|
|
" activated (added to the bundle list).
|
|
"
|
|
" bang -- 0 if the user should be asked to confirm every deletion, 1 if they
|
|
" should be removed unconditionally
|
|
" ---------------------------------------------------------------------------
|
|
func! vundle#installer#clean(bang) abort
|
|
let bundle_dirs = map(copy(g:bundles), 'v:val.path()')
|
|
let all_dirs = (v:version > 702 || (v:version == 702 && has("patch51")))
|
|
\ ? split(globpath(g:bundle_dir, '*', 1), "\n")
|
|
\ : split(globpath(g:bundle_dir, '*'), "\n")
|
|
let x_dirs = filter(all_dirs, '0 > index(bundle_dirs, v:val)')
|
|
|
|
if empty(x_dirs)
|
|
let headers = ['" All clean!']
|
|
let names = []
|
|
else
|
|
let headers = ['" Removing Plugins:']
|
|
let names = vundle#scripts#bundle_names(map(copy(x_dirs), 'fnamemodify(v:val, ":t")'))
|
|
end
|
|
|
|
call vundle#scripts#view('clean', headers, names)
|
|
redraw
|
|
|
|
if (a:bang || empty(names))
|
|
call s:process(a:bang, 'D')
|
|
else
|
|
call inputsave()
|
|
let response = input('Continue? [Y/n]: ')
|
|
call inputrestore()
|
|
if (response =~? 'y' || response == '')
|
|
call s:process(a:bang, 'D')
|
|
endif
|
|
endif
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Delete to directory for a plugin.
|
|
"
|
|
" bang -- not used
|
|
" dir_name -- the bundle directory to be deleted (as a string)
|
|
" return -- 'error' if an error occurred, 'deleted' if the plugin folder was
|
|
" successfully deleted
|
|
" ---------------------------------------------------------------------------
|
|
func! vundle#installer#delete(bang, dir_name) abort
|
|
|
|
let cmd = ((has('win32') || has('win64')) && empty(matchstr(&shell, 'sh'))) ?
|
|
\ 'rmdir /S /Q' :
|
|
\ 'rm -rf'
|
|
|
|
let bundle = vundle#config#init_bundle(a:dir_name, {})
|
|
let cmd .= ' '.vundle#installer#shellesc(bundle.path())
|
|
|
|
let out = s:system(cmd)
|
|
|
|
call s:log('')
|
|
call s:log('Plugin '.a:dir_name)
|
|
call s:log(cmd, '$ ')
|
|
call s:log(out, '> ')
|
|
|
|
if 0 != v:shell_error
|
|
return 'error'
|
|
else
|
|
return 'deleted'
|
|
endif
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Check if a bundled plugin has any documentation.
|
|
"
|
|
" rtp -- a path (string) where the plugin is installed
|
|
" return -- 1 if some documentation was found, 0 otherwise
|
|
" ---------------------------------------------------------------------------
|
|
func! s:has_doc(rtp) abort
|
|
return isdirectory(a:rtp.'/doc')
|
|
\ && (!filereadable(a:rtp.'/doc/tags') || filewritable(a:rtp.'/doc/tags'))
|
|
\ && (v:version > 702 || (v:version == 702 && has("patch51")))
|
|
\ ? !(empty(glob(a:rtp.'/doc/*.txt', 1)) && empty(glob(a:rtp.'/doc/*.??x', 1)))
|
|
\ : !(empty(glob(a:rtp.'/doc/*.txt')) && empty(glob(a:rtp.'/doc/*.??x')))
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Update the helptags for a plugin.
|
|
"
|
|
" rtp -- the path to the plugin's root directory (string)
|
|
" return -- 1 if :helptags succeeded, 0 otherwise
|
|
" ---------------------------------------------------------------------------
|
|
func! s:helptags(rtp) abort
|
|
" it is important to keep trailing slash here
|
|
let doc_path = resolve(a:rtp . '/doc/')
|
|
call s:log(':helptags '.doc_path)
|
|
try
|
|
execute 'helptags ' . doc_path
|
|
catch
|
|
call s:log("> Error running :helptags ".doc_path)
|
|
return 0
|
|
endtry
|
|
return 1
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Get the URL for the remote called 'origin' on the repository that
|
|
" corresponds to a given bundle.
|
|
"
|
|
" bundle -- a bundle object to check the repository for
|
|
" return -- the URL for the origin remote (string)
|
|
" ---------------------------------------------------------------------------
|
|
func! s:get_current_origin_url(bundle) abort
|
|
let cmd = 'cd '.vundle#installer#shellesc(a:bundle.path()).' && git config --get remote.origin.url'
|
|
let cmd = g:shellesc_cd(cmd)
|
|
let out = s:strip(s:system(cmd))
|
|
return out
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Get a short sha of the HEAD of the repository for a given bundle
|
|
"
|
|
" bundle -- a bundle object
|
|
" return -- A 15 character log sha for the current HEAD
|
|
" ---------------------------------------------------------------------------
|
|
func! s:get_current_sha(bundle)
|
|
let cmd = 'cd '.vundle#installer#shellesc(a:bundle.path()).' && git rev-parse HEAD'
|
|
let cmd = g:shellesc_cd(cmd)
|
|
let out = s:system(cmd)[0:15]
|
|
return out
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Create the appropriate sync command to run according to the current state of
|
|
" the local repository (clone, pull, reset, etc).
|
|
"
|
|
" In the case of a pull (update), also return the current sha, so that we can
|
|
" later check that there has been an upgrade.
|
|
"
|
|
" bang -- 0 if only new plugins should be installed, 1 if existing plugins
|
|
" should be updated
|
|
" bundle -- a bundle object to create the sync command for
|
|
" return -- A list containing the command to run and the sha for the current
|
|
" HEAD
|
|
" ---------------------------------------------------------------------------
|
|
func! s:make_sync_command(bang, bundle) abort
|
|
let git_dir = expand(a:bundle.path().'/.git/', 1)
|
|
if isdirectory(git_dir) || filereadable(expand(a:bundle.path().'/.git', 1))
|
|
|
|
let current_origin_url = s:get_current_origin_url(a:bundle)
|
|
if current_origin_url != a:bundle.uri
|
|
call s:log('Plugin URI change detected for Plugin ' . a:bundle.name)
|
|
call s:log('> Plugin ' . a:bundle.name . ' old URI: ' . current_origin_url)
|
|
call s:log('> Plugin ' . a:bundle.name . ' new URI: ' . a:bundle.uri)
|
|
" Directory names match but the origin remotes are not the same
|
|
let cmd_parts = [
|
|
\ 'cd '.vundle#installer#shellesc(a:bundle.path()) ,
|
|
\ 'git remote set-url origin ' . vundle#installer#shellesc(a:bundle.uri),
|
|
\ 'git fetch',
|
|
\ 'git reset --hard origin/HEAD',
|
|
\ 'git submodule update --init --recursive',
|
|
\ ]
|
|
let cmd = join(cmd_parts, ' && ')
|
|
let cmd = g:shellesc_cd(cmd)
|
|
let initial_sha = ''
|
|
return [cmd, initial_sha]
|
|
endif
|
|
|
|
if !(a:bang)
|
|
" The repo exists, and no !, so leave as it is.
|
|
return ['', '']
|
|
endif
|
|
|
|
let cmd_parts = [
|
|
\ 'cd '.vundle#installer#shellesc(a:bundle.path()),
|
|
\ 'git pull',
|
|
\ 'git submodule update --init --recursive',
|
|
\ ]
|
|
let cmd = join(cmd_parts, ' && ')
|
|
let cmd = g:shellesc_cd(cmd)
|
|
|
|
let initial_sha = s:get_current_sha(a:bundle)
|
|
else
|
|
let cmd = 'git clone --recursive '.vundle#installer#shellesc(a:bundle.uri).' '.vundle#installer#shellesc(a:bundle.path())
|
|
let initial_sha = ''
|
|
endif
|
|
return [cmd, initial_sha]
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Install or update a given bundle object with git.
|
|
"
|
|
" bang -- 0 if only new plugins should be installed, 1 if existing plugins
|
|
" should be updated
|
|
" bundle -- a bundle object (dictionary)
|
|
" return -- a string indicating the status of the bundle installation:
|
|
" - todate : Nothing was updated or the repository was up to date
|
|
" - new : The plugin was newly installed
|
|
" - updated : Some changes where pulled via git
|
|
" - error : An error occurred in the shell command
|
|
" - pinned : The bundle is marked as pinned
|
|
" ---------------------------------------------------------------------------
|
|
func! s:sync(bang, bundle) abort
|
|
" Do not sync if this bundle is pinned
|
|
if a:bundle.is_pinned()
|
|
return 'pinned'
|
|
endif
|
|
|
|
let [ cmd, initial_sha ] = s:make_sync_command(a:bang, a:bundle)
|
|
if empty(cmd)
|
|
return 'todate'
|
|
endif
|
|
|
|
let out = s:system(cmd)
|
|
call s:log('')
|
|
call s:log('Plugin '.a:bundle.name_spec)
|
|
call s:log(cmd, '$ ')
|
|
call s:log(out, '> ')
|
|
|
|
if 0 != v:shell_error
|
|
return 'error'
|
|
end
|
|
|
|
if empty(initial_sha)
|
|
return 'new'
|
|
endif
|
|
|
|
let updated_sha = s:get_current_sha(a:bundle)
|
|
|
|
if initial_sha == updated_sha
|
|
return 'todate'
|
|
endif
|
|
|
|
call add(g:updated_bundles, [initial_sha, updated_sha, a:bundle])
|
|
return 'updated'
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Escape special characters in a string to be able to use it as a shell
|
|
" command with system().
|
|
"
|
|
" cmd -- the string holding the shell command
|
|
" return -- a string with the relevant characters escaped
|
|
" ---------------------------------------------------------------------------
|
|
func! vundle#installer#shellesc(cmd) abort
|
|
if ((has('win32') || has('win64')) && empty(matchstr(&shell, 'sh')))
|
|
return '"' . substitute(a:cmd, '"', '\\"', 'g') . '"'
|
|
endif
|
|
return shellescape(a:cmd)
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Fix a cd shell command to be used on Windows.
|
|
"
|
|
" cmd -- the command to be fixed (string)
|
|
" return -- the fixed command (string)
|
|
" ---------------------------------------------------------------------------
|
|
func! g:shellesc_cd(cmd) abort
|
|
if ((has('win32') || has('win64')) && empty(matchstr(&shell, 'sh')))
|
|
let cmd = substitute(a:cmd, '^cd ','cd /d ','') " add /d switch to change drives
|
|
return cmd
|
|
else
|
|
return a:cmd
|
|
endif
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Make a system call. This can be used to change the way system calls
|
|
" are made during developing, without searching the whole code base for
|
|
" actual system() calls.
|
|
"
|
|
" cmd -- the command passed to system() (string)
|
|
" return -- the return value from system()
|
|
" ---------------------------------------------------------------------------
|
|
func! s:system(cmd) abort
|
|
return system(a:cmd)
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Add a log message to Vundle's internal logging variable.
|
|
"
|
|
" str -- the log message (string)
|
|
" prefix -- optional prefix for multi-line entries (string)
|
|
" return -- a:str
|
|
" ---------------------------------------------------------------------------
|
|
func! s:log(str, ...) abort
|
|
let prefix = a:0 > 0 ? a:1 : ''
|
|
let fmt = '%Y-%m-%d %H:%M:%S'
|
|
let lines = split(a:str, '\n', 1)
|
|
let time = strftime(fmt)
|
|
for line in lines
|
|
call add(g:vundle_log, '['. time .'] '. prefix . line)
|
|
endfor
|
|
return a:str
|
|
endf
|
|
|
|
|
|
" ---------------------------------------------------------------------------
|
|
" Remove leading and trailing whitespace from a string
|
|
"
|
|
" str -- The string to rid of trailing and leading spaces
|
|
" return -- A string stripped of side spaces
|
|
" ---------------------------------------------------------------------------
|
|
func! s:strip(str)
|
|
return substitute(a:str, '\%^\_s*\(.\{-}\)\_s*\%$', '\1', '')
|
|
endf
|
|
|
|
" vim: set expandtab sts=2 ts=2 sw=2 tw=78 norl:
|