diff --git a/autoload/vundle.vim b/autoload/vundle.vim index 16eebca..781abc3 100644 --- a/autoload/vundle.vim +++ b/autoload/vundle.vim @@ -14,9 +14,6 @@ com! -nargs=? -bang -complete=custom,vundle#scripts#complete PluginInstall com! -nargs=? -bang -complete=custom,vundle#scripts#complete PluginSearch \ call vundle#scripts#all('!' == '', ) -com! -nargs=? -bang -complete=custom,vundle#scripts#complete Plugins -\ call vundle#scripts#all('!' == '', ) - com! -nargs=0 -bang PluginList \ call vundle#installer#list('!' == '') @@ -36,27 +33,32 @@ com! -nargs=? -bang VundleClean Plugi com! -nargs=0 VundleDocs PluginDocs com! VundleUpdate PluginInstall! -" deprecated +" Deprecated Commands com! -nargs=+ Bundle call vundle#config#bundle() com! -nargs=? -bang -complete=custom,vundle#scripts#complete BundleInstall PluginInstall com! -nargs=? -bang -complete=custom,vundle#scripts#complete BundleSearch PluginSearch -com! -nargs=? -bang -complete=custom,vundle#scripts#complete Bundles Plugins com! -nargs=0 -bang BundleList PluginList com! -nargs=? -bang BundleClean PluginClean com! -nargs=0 BundleDocs PluginDocs com! BundleUpdate PluginInstall! +" Set up the signs used in the installer window. (See :help signs) if (has('signs')) -sign define Vu_error text=! texthl=Error -sign define Vu_active text=> texthl=Comment -sign define Vu_todate text=. texthl=Comment -sign define Vu_new text=+ texthl=Comment -sign define Vu_updated text=* texthl=Comment -sign define Vu_deleted text=- texthl=Comment -sign define Vu_helptags text=* texthl=Comment + sign define Vu_error text=! texthl=Error + sign define Vu_active text=> texthl=Comment + sign define Vu_todate text=. texthl=Comment + sign define Vu_new text=+ texthl=Comment + sign define Vu_updated text=* texthl=Comment + sign define Vu_deleted text=- texthl=Comment + sign define Vu_helptags text=* texthl=Comment + sign define Vu_pinned text== texthl=Comment endif - +" Set up Vundle. This function has to be called from the users vimrc file. +" This will force Vim to source this file as a side effect which wil define +" the :Plugin command. After calling this function the user can use the +" :Plugin command in the vimrc. It is not possible to do this automatically +" because when loading the vimrc file no plugins where loaded yet. func! vundle#rc(...) abort let g:bundle_dir = len(a:000) > 0 ? expand(a:1, 1) : expand('$HOME/.vim/bundle', 1) let g:updated_bundles = [] @@ -64,3 +66,13 @@ func! vundle#rc(...) abort let g:vundle_changelog = ['Updated Plugins:'] call vundle#config#init() endf + +func! vundle#begin(...) abort + let g:vundle_lazy_load = 1 + call call('vundle#rc', a:000) +endf + +func! vundle#end(...) abort + unlet g:vundle_lazy_load + call vundle#config#activate_bundles() +endf diff --git a/autoload/vundle/config.vim b/autoload/vundle/config.vim index 7bcf647..b04e5bf 100644 --- a/autoload/vundle/config.vim +++ b/autoload/vundle/config.vim @@ -1,17 +1,57 @@ +" --------------------------------------------------------------------------- +" Add a plugin to the runtimepath. +" +" arg -- a string specifying the plugin +" ... -- a dictionary of options for the plugin +" return -- the return value from vundle#config#init_bundle() +" --------------------------------------------------------------------------- func! vundle#config#bundle(arg, ...) let bundle = vundle#config#init_bundle(a:arg, a:000) - call s:rtp_rm_a() - call add(g:bundles, bundle) - call s:rtp_add_a() + if !s:check_bundle_name(bundle) + return + endif + if exists('g:vundle_lazy_load') && g:vundle_lazy_load + call add(g:bundles, bundle) + else + call s:rtp_rm_a() + call add(g:bundles, bundle) + call s:rtp_add_a() + call s:rtp_add_defaults() + endif return bundle endf + +" --------------------------------------------------------------------------- +" When lazy bundle load is used (begin/end functions), add all configured +" bundles to runtimepath and reorder appropriately. +" --------------------------------------------------------------------------- +func! vundle#config#activate_bundles() + call s:rtp_add_a() + call s:rtp_add_defaults() +endf + + +" --------------------------------------------------------------------------- +" Initialize Vundle. +" +" Start a new bundles list and make sure the runtimepath does not contain +" directories from a previous call. In theory, this should only be called +" once. +" --------------------------------------------------------------------------- func! vundle#config#init() if !exists('g:bundles') | let g:bundles = [] | endif call s:rtp_rm_a() let g:bundles = [] + let g:bundle_names = {} endf + +" --------------------------------------------------------------------------- +" Add a list of bundles to the runtimepath and source them. +" +" bundles -- a list of bundle objects +" --------------------------------------------------------------------------- func! vundle#config#require(bundles) abort for b in a:bundles call s:rtp_add(b.rtpath) @@ -21,8 +61,17 @@ func! vundle#config#require(bundles) abort exec 'runtime! '.b.name.'/after/*.vim' call s:rtp_rm(g:bundle_dir) endfor + call s:rtp_add_defaults() endf + +" --------------------------------------------------------------------------- +" Create a bundle object from a bundle specification. +" +" name -- the bundle specification as a string +" opts -- the options dictionary from then bundle definition +" return -- an initialized bundle object +" --------------------------------------------------------------------------- func! vundle#config#init_bundle(name, opts) if a:name != substitute(a:name, '^\s*\(.\{-}\)\s*$', '\1', '') echo "Spurious leading and/or trailing whitespace found in plugin spec '" . a:name . "'" @@ -33,6 +82,35 @@ func! vundle#config#init_bundle(name, opts) return b endf + +" --------------------------------------------------------------------------- +" Check if the current bundle name has already been used in this running +" instance and show an error to that effect. +" +" bundle -- a bundle object whose name is to be checked +" return -- 0 if the bundle's name has been seen before, 1 otherwise +" --------------------------------------------------------------------------- +funct! s:check_bundle_name(bundle) + if has_key(g:bundle_names, a:bundle.name) + echoerr 'Vundle error: Name collision for Plugin ' . a:bundle.name_spec . + \ '. Plugin ' . g:bundle_names[a:bundle.name] . + \ ' previously used the name "' . a:bundle.name . '"' . + \ '. Skipping Plugin ' . a:bundle.name_spec . '.' + return 0 + endif + let g:bundle_names[a:bundle.name] = a:bundle.name_spec + return 1 +endf + + +" --------------------------------------------------------------------------- +" Parse the options which can be supplied with the bundle specification. +" Corresponding documentation: vundle-plugins-configure +" +" opts -- a dictionary with the user supplied options for the bundle +" return -- a dictionary with the user supplied options for the bundle, this +" will be merged with a s:bundle object into one dictionary. +" --------------------------------------------------------------------------- func! s:parse_options(opts) " TODO: improve this if len(a:opts) != 1 | return {} | endif @@ -44,6 +122,16 @@ func! s:parse_options(opts) endif endf + +" --------------------------------------------------------------------------- +" Parse the plugin specification. Corresponding documentation: +" vundle-plugins-uris +" +" arg -- the string supplied to identify the plugin +" return -- a dictionary with the folder name (key 'name') and the uri (key +" 'uri') for cloning the plugin and the original argument (key +" 'name_spec') +" --------------------------------------------------------------------------- func! s:parse_name(arg) let arg = a:arg let git_proto = exists('g:vundle_default_git_proto') ? g:vundle_default_git_proto : 'https' @@ -67,6 +155,32 @@ func! s:parse_name(arg) return {'name': name, 'uri': uri, 'name_spec': arg } endf + +" --------------------------------------------------------------------------- +" Modify the runtimepath, after all bundles have been added, so that the +" directories that were in the default runtimepath appear first in the list +" (with their 'after' directories last). +" --------------------------------------------------------------------------- +func! s:rtp_add_defaults() + let current = &rtp + set rtp&vim + let default = &rtp + let &rtp = current + for item in reverse(split(default, ',')) + exec 'set rtp-=' . item + if fnamemodify(item, ":t") == 'after' + exec 'set rtp+=' . item + else + exec 'set rtp^=' . item + endif + endfor +endf + + +" --------------------------------------------------------------------------- +" Remove all paths for the plugins which are managed by Vundle from the +" runtimepath. +" --------------------------------------------------------------------------- func! s:rtp_rm_a() let paths = map(copy(g:bundles), 'v:val.rtpath') let prepends = join(paths, ',') @@ -75,6 +189,11 @@ func! s:rtp_rm_a() exec 'set rtp-='.fnameescape(appends) endf + +" --------------------------------------------------------------------------- +" Add all paths for the plugins which are managed by Vundle to the +" runtimepath. +" --------------------------------------------------------------------------- func! s:rtp_add_a() let paths = map(copy(g:bundles), 'v:val.rtpath') let prepends = join(paths, ',') @@ -83,27 +202,78 @@ func! s:rtp_add_a() exec 'set rtp+='.fnameescape(appends) endf + +" --------------------------------------------------------------------------- +" Remove a directory and the corresponding 'after' directory from runtimepath. +" +" dir -- the directory name to be removed as a string. The corresponding +" 'after' directory will also be removed. +" --------------------------------------------------------------------------- func! s:rtp_rm(dir) abort exec 'set rtp-='.fnameescape(expand(a:dir, 1)) exec 'set rtp-='.fnameescape(expand(a:dir.'/after', 1)) endf + +" --------------------------------------------------------------------------- +" Add a directory and the corresponding 'after' directory to runtimepath. +" +" dir -- the directory name to be added as a string. The corresponding +" 'after' directory will also be added. +" --------------------------------------------------------------------------- func! s:rtp_add(dir) abort exec 'set rtp^='.fnameescape(expand(a:dir, 1)) exec 'set rtp+='.fnameescape(expand(a:dir.'/after', 1)) endf + +" --------------------------------------------------------------------------- +" Expand and simplify a path. +" +" path -- the path to expand as a string +" return -- the expanded and simplified path +" --------------------------------------------------------------------------- func! s:expand_path(path) abort return simplify(expand(a:path, 1)) endf + +" --------------------------------------------------------------------------- +" Find the actual path inside a bundle directory to be added to the +" runtimepath. It might be provided by the user with the 'rtp' option. +" Corresponding documentation: vundle-plugins-configure +" +" opts -- a bundle dict +" return -- expanded path to the corresponding plugin directory +" --------------------------------------------------------------------------- func! s:rtpath(opts) return has_key(a:opts, 'rtp') ? s:expand_path(a:opts.path().'/'.a:opts.rtp) : a:opts.path() endf + +" --------------------------------------------------------------------------- +" a bundle 'object' +" --------------------------------------------------------------------------- let s:bundle = {} + +" --------------------------------------------------------------------------- +" Return the absolute path to the directory inside the bundle directory +" (prefix) where thr bundle will be cloned. +" +" return -- the target location to clone this bundle to +" --------------------------------------------------------------------------- func! s:bundle.path() return s:expand_path(g:bundle_dir.'/'.self.name) endf + +" --------------------------------------------------------------------------- +" Determine if the bundle has the pinned attribute set in the config +" +" return -- 1 if the bundle is pinned, 0 otherwise +" --------------------------------------------------------------------------- +func! s:bundle.is_pinned() + return get(self, 'pinned') +endf + diff --git a/autoload/vundle/installer.vim b/autoload/vundle/installer.vim index bc2f41d..7972cfd 100644 --- a/autoload/vundle/installer.vim +++ b/autoload/vundle/installer.vim @@ -1,3 +1,10 @@ +" --------------------------------------------------------------------------- +" 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 : @@ -6,12 +13,23 @@ func! vundle#installer#new(bang, ...) abort 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 = '' @@ -43,6 +61,16 @@ func! s:process(bang, cmd) 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 @@ -67,6 +95,8 @@ func! vundle#installer#run(func_name, name, ...) abort echo n.' deleted' elseif 'helptags' == status echo n.' regenerated' + elseif 'pinned' == status + echo n.' pinned' elseif 'error' == status echohl Error echo 'Error processing '.n @@ -81,6 +111,13 @@ func! vundle#installer#run(func_name, name, ...) abort 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 @@ -89,6 +126,14 @@ func! s:sign(status) 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, {}) @@ -97,6 +142,14 @@ func! vundle#installer#install_and_require(bang, name) abort 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 @@ -112,6 +165,12 @@ func! vundle#installer#install(bang, name) abort 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 @@ -120,6 +179,14 @@ func! vundle#installer#docs() abort 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)') @@ -135,6 +202,13 @@ func! vundle#installer#helptags(bundles) abort 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) @@ -143,6 +217,13 @@ func! vundle#installer#list(bang) abort 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"))) @@ -174,6 +255,14 @@ func! vundle#installer#clean(bang) abort 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'))) ? @@ -187,8 +276,8 @@ func! vundle#installer#delete(bang, dir_name) abort call s:log('') call s:log('Plugin '.a:dir_name) - call s:log('$ '.cmd) - call s:log('> '.out) + call s:log(cmd, '$ ') + call s:log(out, '> ') if 0 != v:shell_error return 'error' @@ -197,6 +286,13 @@ func! vundle#installer#delete(bang, dir_name) abort 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')) @@ -205,6 +301,13 @@ func! s:has_doc(rtp) abort \ : !(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/') @@ -218,12 +321,69 @@ func! s:helptags(rtp) abort return 1 endf -func! s:sync(bang, bundle) abort + +" --------------------------------------------------------------------------- +" 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 + + +" --------------------------------------------------------------------------- +" 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)) - if !(a:bang) | return 'todate' | endif - let cmd = 'cd '.vundle#installer#shellesc(a:bundle.path()).' && git pull && git submodule update --init --recursive' + 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 get_current_sha = 'cd '.vundle#installer#shellesc(a:bundle.path()).' && git rev-parse HEAD' @@ -233,12 +393,39 @@ func! s:sync(bang, bundle) abort 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) + call s:log(cmd, '$ ') + call s:log(out, '> ') if 0 != v:shell_error return 'error' @@ -258,6 +445,14 @@ func! s:sync(bang, bundle) abort 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') . '"' @@ -265,6 +460,13 @@ func! vundle#installer#shellesc(cmd) abort 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 @@ -274,12 +476,45 @@ func! g:shellesc_cd(cmd) abort 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 -func! s:log(str) abort - let fmt = '%y%m%d %H:%M:%S' - call add(g:vundle_log, '['.strftime(fmt).'] '.a:str) + +" --------------------------------------------------------------------------- +" 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 diff --git a/autoload/vundle/scripts.vim b/autoload/vundle/scripts.vim index 5c43547..40f91b9 100644 --- a/autoload/vundle/scripts.vim +++ b/autoload/vundle/scripts.vim @@ -1,3 +1,11 @@ +" --------------------------------------------------------------------------- +" Search the database from vim-script.org for a matching plugin. If no +" argument is given, list all plugins. This function is used by the :Plugins +" and :PluginSearch commands. +" +" bang -- if 1 refresh the script name cache, if 0 don't +" ... -- a plugin name to search for +" --------------------------------------------------------------------------- func! vundle#scripts#all(bang, ...) let b:match = '' let info = ['"Keymap: i - Install plugin; c - Cleanup; s - Search; R - Reload list'] @@ -13,15 +21,31 @@ func! vundle#scripts#all(bang, ...) echo len(matches).' plugins found' endf + +" --------------------------------------------------------------------------- +" Repeat the search for bundles. +" --------------------------------------------------------------------------- func! vundle#scripts#reload() abort silent exec ':PluginSearch! '.(exists('b:match') ? b:match : '') redraw endf + +" --------------------------------------------------------------------------- +" Complete names for bundles in the command line. +" +" a, c, d -- see :h command-completion-custom +" return -- all valid plugin names from vim-scripts.org as completion +" candidates, see also :h command-completion-custom +" --------------------------------------------------------------------------- func! vundle#scripts#complete(a,c,d) return join(s:load_scripts(0),"\n") endf + +" --------------------------------------------------------------------------- +" View the logfile after an update or installation. +" --------------------------------------------------------------------------- func! s:view_log() if !exists('g:vundle_log_file') let g:vundle_log_file = tempname() @@ -33,6 +57,11 @@ func! s:view_log() wincmd P | wincmd H endf + +" --------------------------------------------------------------------------- +" Parse the output from git log after an update to create a change log for the +" user. +" --------------------------------------------------------------------------- func! s:create_changelog() abort for bundle_data in g:updated_bundles let initial_sha = bundle_data[0] @@ -61,6 +90,10 @@ func! s:create_changelog() abort endfor endf + +" --------------------------------------------------------------------------- +" View the change log after an update or installation. +" --------------------------------------------------------------------------- func! s:view_changelog() call s:create_changelog() @@ -74,10 +107,27 @@ func! s:view_changelog() wincmd P | wincmd H endf + +" --------------------------------------------------------------------------- +" Create a list of 'Plugin ...' lines from a list of bundle names. +" +" names -- a list of names (strings) of plugins +" return -- a list of 'Plugin ...' lines suitable to be written to a buffer +" --------------------------------------------------------------------------- func! vundle#scripts#bundle_names(names) return map(copy(a:names), ' printf("Plugin ' ."'%s'".'", v:val) ') endf + +" --------------------------------------------------------------------------- +" Open a buffer to display information to the user. Several special commands +" are defined in the new buffer. +" +" title -- a title for the new buffer +" headers -- a list of header lines to be displayed at the top of the buffer +" results -- the main information to be displayed in the buffer (list of +" strings) +" --------------------------------------------------------------------------- func! vundle#scripts#view(title, headers, results) if exists('g:vundle_view') && bufloaded(g:vundle_view) exec g:vundle_view.'bd!' @@ -148,6 +198,13 @@ func! vundle#scripts#view(title, headers, results) exec ':'.(len(a:headers) + 1) endf + +" --------------------------------------------------------------------------- +" Load the plugin database from vim-scripts.org . +" +" to -- the filename (string) to save the database to +" return -- 0 on success, 1 if an error occurred +" --------------------------------------------------------------------------- func! s:fetch_scripts(to) let scripts_dir = fnamemodify(expand(a:to, 1), ":h") if !isdirectory(scripts_dir) @@ -178,6 +235,15 @@ func! s:fetch_scripts(to) return 0 endf + +" --------------------------------------------------------------------------- +" Load the plugin database and return a list of all plugins. +" +" bang -- if 1 download the redatabase, else only download if it is not +" readable on disk (i.e. does not exist) +" return -- a list of strings, these are the names (valid bundle +" specifications) of all plugins from vim-scripts.org +" --------------------------------------------------------------------------- func! s:load_scripts(bang) let f = expand(g:bundle_dir.'/.vundle/script-names.vim-scripts.org.json', 1) if a:bang || !filereadable(f) diff --git a/doc/vundle.txt b/doc/vundle.txt index a6c35e2..3cffe91 100644 --- a/doc/vundle.txt +++ b/doc/vundle.txt @@ -166,6 +166,26 @@ This can be used to prevent name collisions between plugins that Vundle would otherwise try to clone into the same directory. It also provides an additional level of customisation. +The 'pinned' option +------------------- + +A flag that, when set to a value of 1, tells Vundle not to perform any git +operations on the plugin, while still adding the existing plugin under the +`bundles` directories to the |runtimepath|. + +For example: +> + Plugin 'mylocalplugin', {'pinned': 1} + +This allows the users to include, with Vundle, plugins tracked with version +control systems other than git, but the user is responsible for cloning and +keeping up to date. It also allows the users to stay in the current version of +a plugin that might have previously been updated by Vundle. + +Please note that the URI will be treated the same as for any other plugins, so +only the last part of it will be added to the |runtimepath|. The user is +advised to use this flag only with single word URIs to avoid confusion. + 3.2 SUPPORTED URIS ~ *vundle-plugins-uris* @@ -185,7 +205,7 @@ GitHub is used when a user/repo is passed to `Plugin`. Vim Scripts ----------- -Any single word without a '/' is assumed to be from Vim Scripts. +Any single word without a slash '/' is assumed to be from Vim Scripts. > Plugin 'ctrlp.vim' => https://github.com/vim-scripts/ctrlp.vim diff --git a/test/vimrc b/test/vimrc index 13dd2e2..b4f2676 100644 --- a/test/vimrc +++ b/test/vimrc @@ -58,6 +58,13 @@ Bundle 'vim-scripts/RubySinatra' " syntax issue #203 Bundle 'jimenezrick/vimerl' +" Static bundle: Same name as a valid vim-scripts bundle +Bundle 'latte', {'pinned' : 1} +if !isdirectory(expand(bundle_dir) . '/latte') + call mkdir(expand(bundle_dir) . '/latte', 'p') +endif + + filetype plugin indent on " Automatically detect file types. set wildignore+=doc " should not break helptags