"============================================================================= " FILE: install.vim " AUTHOR: Shougo Matsushita " License: MIT license "============================================================================= " Variables let s:global_context = {} let s:log = [] let s:updates_log = [] let s:progress = '' " Global options definition. let g:dein#install_max_processes = \ get(g:, 'dein#install_max_processes', \ dein#util#_is_windows() ? 16 : 8) let g:dein#install_progress_type = \ get(g:, 'dein#install_progress_type', 'echo') let g:dein#install_message_type = \ get(g:, 'dein#install_message_type', 'echo') let g:dein#install_process_timeout = \ get(g:, 'dein#install_process_timeout', 120) let g:dein#install_log_filename = \ get(g:, 'dein#install_log_filename', '') let g:dein#install_github_api_token = \ get(g:, 'dein#install_github_api_token', '') let g:dein#install_curl_command = \ get(g:, 'dein#install_curl_command', 'curl') let g:dein#install_check_diff = \ get(g:, 'dein#install_check_diff', v:false) function! s:get_job() abort if !exists('s:Job') let s:Job = vital#dein#import('System.Job') endif return s:Job endfunction function! dein#install#_update(plugins, update_type, async) abort if g:dein#_is_sudo call s:error('update/install is disabled in sudo session.') return endif let plugins = dein#util#_get_plugins(a:plugins) if a:update_type ==# 'install' let plugins = filter(plugins, { _, val -> !isdirectory(val.path) }) endif if a:async && !empty(s:global_context) && \ confirm('The installation has not finished. Cancel now?', \ "yes\nNo", 2) != 1 return endif " Set context. let context = s:init_context(plugins, a:update_type, a:async) call s:init_variables(context) if empty(plugins) call s:notify('Target plugins are not found.') call s:notify('You may have used the wrong plugin name,'. \ ' or all of the plugins are already installed.') let s:global_context = {} return endif call s:start() if !a:async || has('vim_starting') return s:update_loop(context) endif augroup dein-install autocmd! augroup END if exists('s:timer') call timer_stop(s:timer) unlet s:timer endif let s:timer = timer_start(50, {-> dein#install#_polling()}, {'repeat': -1}) endfunction function! s:update_loop(context) abort let errored = 0 try if has('vim_starting') while !empty(s:global_context) let errored = s:install_async(a:context) sleep 50ms redraw endwhile else let errored = s:install_blocking(a:context) endif catch call s:error(v:exception) call s:error(v:throwpoint) return 1 endtry return errored endfunction function! dein#install#_check_update(plugins, force, async) abort if g:dein#install_github_api_token ==# '' call s:error('You need to set g:dein#install_github_api_token' . \ ' for the feature.') return endif if !executable(g:dein#install_curl_command) call s:error('curl must be executable for the feature.') return endif let s:global_context.progress_type = 'echo' let query_max = 100 let plugins = dein#util#_get_plugins(a:plugins) let processes = [] for index in range(0, len(plugins) - 1, query_max) redraw call s:print_progress_message( \s:get_progress_message('', index, len(plugins))) let query = '' for plug_index in range(index, \ min([index + query_max, len(plugins)]) - 1) let plugin_names = split(plugins[plug_index].repo, '/') if len(plugin_names) < 2 " Invalid repository name. continue endif " Note: "repository" API is faster than "search" API let query .= printf('a%d:repository(owner:\"%s\", name: \"%s\")' . \ '{ pushedAt nameWithOwner }', \ plug_index, plugin_names[-2], plugin_names[-1]) endfor let commands = [ \ g:dein#install_curl_command, '-H', 'Authorization: bearer ' . \ g:dein#install_github_api_token, \ '-X', 'POST', '-d', \ '{ "query": "query {' . query . '}" }', \ 'https://api.github.com/graphql' \ ] let process = {'candidates': []} function! process.on_out(data) abort let candidates = self.candidates if empty(candidates) call add(candidates, a:data[0]) else let candidates[-1] .= a:data[0] endif let candidates += a:data[1:] endfunction let process.job = s:get_job().start( \ s:convert_args(commands), \ {'on_stdout': function(process.on_out, [], process)}) call add(processes, process) endfor " Get outputs let results = [] for process in processes call process.job.wait(g:dein#install_process_timeout * 1000) if !empty(process.candidates) let result = process.candidates[0] try let json = json_decode(result) let results += filter(values(json['data']), \ { _, val -> type(val) == v:t_dict \ && has_key(val, 'pushedAt') }) catch call s:error('json output decode error: ' + string(result)) endtry endif endfor " Get pushed time. let check_pushed = {} for node in results let format = '%Y-%m-%dT%H:%M:%SZ' let pushed_at = node['pushedAt'] let check_pushed[node['nameWithOwner']] = \ exists('*strptime') ? \ strptime(format, pushed_at) : \ dein#DateTime#from_format(pushed_at, format).unix_time() endfor " Get the last updated time by rollbackfile timestamp. " Note: .git timestamp may be changed by git commands. let rollbacks = reverse(sort(glob( \ s:get_rollback_directory() . '/*', v:true, v:true))) let rollback_time = empty(rollbacks) ? -1 : getftime(rollbacks[0]) " Compare with .git directory updated time. let updated = [] for plugin in plugins if !has_key(check_pushed, plugin.repo) continue endif let git_path = plugin.path . '/.git' let repo_time = isdirectory(plugin.path) ? getftime(git_path) : -1 if min([repo_time, rollback_time]) < check_pushed[plugin.repo] call add(updated, plugin) endif endfor redraw | echo '' " Clear global context let s:global_context = {} if empty(updated) call s:notify(strftime('Done: (%Y/%m/%d %H:%M:%S)')) return endif " Note: Use echo to display it in confirm call s:echo('Updated plugins: ' . \ string(map(copy(updated), { _, val -> val.name })), 'echo') if !a:force && confirm( \ 'Updated plugins are exists. Update now?', "yes\nNo", 2) != 1 return endif call dein#install#_update(updated, 'update', a:async) endfunction function! dein#install#_reinstall(plugins) abort if g:dein#_is_sudo call s:error('update/install is disabled in sudo session.') return endif let plugins = dein#util#_get_plugins(a:plugins) for plugin in plugins " Remove the plugin if plugin.type ==# 'none' \ || get(plugin, 'local', 0) \ || (plugin.sourced && \ index(['dein'], plugin.normalized_name) >= 0) call dein#util#_error( \ printf('|%s| Cannot reinstall the plugin!', plugin.name)) continue endif " Reinstall. call s:print_progress_message(printf('|%s| Reinstalling...', plugin.name)) if isdirectory(plugin.path) call dein#install#_rm(plugin.path) endif endfor call dein#install#_update(dein#util#_convert2list(a:plugins), \ 'install', 0) endfunction function! dein#install#_direct_install(repo, options) abort if g:dein#_is_sudo call s:error('update/install is disabled in sudo session.') return endif let options = copy(a:options) let options.merged = 0 let plugin = dein#add(a:repo, options) if empty(plugin) return endif call dein#install#_update(plugin.name, 'install', 0) call dein#source(plugin.name) " Add to direct_install.vim let file = dein#get_direct_plugins_path() let line = printf('call dein#add(%s, %s)', \ string(a:repo), string(options)) if !filereadable(file) call dein#util#_safe_writefile([line], file) else call dein#util#_safe_writefile(add(readfile(file), line), file) endif endfunction function! dein#install#_rollback(date, plugins) abort if g:dein#_is_sudo call s:error('update/install is disabled in sudo session.') return endif let glob = s:get_rollback_directory() . '/' . a:date . '*' let rollbacks = reverse(sort(glob(glob, v:true, v:true))) if empty(rollbacks) return endif call dein#install#_load_rollback(rollbacks[0], a:plugins) endfunction function! dein#install#_recache_runtimepath() abort if g:dein#_is_sudo return endif let start = reltime() " Clear runtime path. call s:clear_runtimepath() let plugins = values(dein#get()) let merged_plugins = filter(copy(plugins), { _, val -> val.merged }) let lazy_merged_plugins = filter(copy(merged_plugins), \ { _, val -> val.lazy }) let nolazy_merged_plugins = filter(copy(merged_plugins), \ { _, val -> !val.lazy }) let merge_ftdetect_plugins = filter(copy(plugins), \ { _, val -> get(val, 'merge_ftdetect', 0) \ || (val.merged && !val.lazy) }) call s:copy_files(lazy_merged_plugins, '') let runtime = dein#util#_get_runtime_path() " Remove plugin directory call dein#install#_rm(runtime . '/plugin') call dein#install#_rm(runtime . '/after/plugin') call s:copy_files(nolazy_merged_plugins, '') call s:helptags() call s:generate_ftplugin() " Clear ftdetect and after/ftdetect directories. call dein#install#_rm(runtime . '/ftdetect') call dein#install#_rm(runtime . '/after/ftdetect') call s:merge_files(merge_ftdetect_plugins, 'ftdetect') call s:merge_files(merge_ftdetect_plugins, 'after/ftdetect') silent call dein#remote_plugins() call dein#call_hook('post_source') call dein#install#_save_rollback( \ s:get_rollback_directory() . '/' . strftime('%Y%m%d%H%M%S'), []) call dein#util#_clear_state() call s:log(strftime('Runtimepath updated: (%Y/%m/%d %H:%M:%S)')) call s:log('recache_runtimepath: ' . split(reltimestr(reltime(start)))[0]) endfunction function! s:clear_runtimepath() abort if dein#util#_get_cache_path() ==# '' call dein#util#_error('Invalid base path.') return endif let runtimepath = dein#util#_get_runtime_path() " Remove runtime path call dein#install#_rm(runtimepath) if !isdirectory(runtimepath) " Create runtime path call dein#util#_safe_mkdir(runtimepath) endif endfunction function! s:helptags() abort if g:dein#_runtime_path ==# '' return '' endif try let tags = dein#util#_get_runtime_path() . '/doc' call dein#util#_safe_mkdir(tags) call s:copy_files(filter(values(dein#get()), \ { _, val -> !val.merged }), 'doc') silent execute 'helptags' fnameescape(tags) catch /^Vim(helptags):E151:/ " Ignore an error that occurs when there is no help file catch call s:error('Error generating helptags:') call s:error(v:exception) call s:error(v:throwpoint) endtry endfunction function! s:copy_files(plugins, directory) abort let directory = (a:directory ==# '' ? '' : '/' . a:directory) let srcs = filter(map(copy(a:plugins), { _, val -> val.rtp . directory }), \ { _, val -> isdirectory(val) }) let stride = 50 for start in range(0, len(srcs), stride) call dein#install#_copy_directories(srcs[start : start + stride-1], \ dein#util#_get_runtime_path() . directory) endfor endfunction function! s:merge_files(plugins, directory) abort let files = [] for plugin in a:plugins for file in filter(globpath( \ plugin.rtp, a:directory.'/**', v:true, v:true), \ { _, val -> !isdirectory(val) }) let files += readfile(file, ':t') endfor endfor if !empty(files) call dein#util#_cache_writefile(files, \ printf('.dein/%s/%s.vim', a:directory, a:directory)) endif endfunction function! dein#install#_save_rollback(rollbackfile, plugins) abort let revisions = {} for plugin in filter(dein#util#_get_plugins(a:plugins), \ { _, val -> s:check_rollback(val) }) let rev = s:get_revision_number(plugin) if rev !=# '' let revisions[plugin.name] = rev endif endfor call dein#util#_safe_writefile( \ [json_encode(revisions)], expand(a:rollbackfile)) endfunction function! dein#install#_load_rollback(rollbackfile, plugins) abort let revisions = json_decode(readfile(a:rollbackfile)[0]) let plugins = dein#util#_get_plugins(a:plugins) call filter(plugins, { _, val -> has_key(revisions, val.name) \ && has_key(dein#util#_get_type(val.type), \ 'get_rollback_command') \ && s:check_rollback(val) \ && s:get_revision_number(val) !=# revisions[val.name] \ }) if empty(plugins) return endif for plugin in plugins let type = dein#util#_get_type(plugin.type) let cmd = type.get_rollback_command( \ dein#util#_get_type(plugin.type), revisions[plugin.name]) call dein#install#_each(cmd, plugin) endfor call dein#recache_runtimepath() call s:error('Rollback to '.fnamemodify(a:rollbackfile, ':t').' version.') endfunction function! s:get_rollback_directory() abort let parent = printf('%s/rollbacks/%s', \ dein#util#_get_cache_path(), g:dein#_progname) call dein#util#_safe_mkdir(parent) return parent endfunction function! s:check_rollback(plugin) abort return !has_key(a:plugin, 'local') && !get(a:plugin, 'frozen', 0) endfunction function! dein#install#_get_default_ftplugin() abort return [ \ 'if exists("g:did_load_ftplugin")', \ ' finish', \ 'endif', \ 'let g:did_load_ftplugin = 1', \ '', \ 'augroup filetypeplugin', \ ' autocmd FileType * call s:ftplugin()', \ 'augroup END', \ '', \ 'function! s:ftplugin()', \ ' if exists("b:undo_ftplugin")', \ ' silent! execute b:undo_ftplugin', \ ' unlet! b:undo_ftplugin b:did_ftplugin', \ ' endif', \ '', \ ' let filetype = expand("")', \ ' if filetype !=# ""', \ ' if &cpoptions =~# "S" && exists("b:did_ftplugin")', \ ' unlet b:did_ftplugin', \ ' endif', \ ' for ft in split(filetype, ''\.'')', \ ' execute "runtime! ftplugin/" . ft . ".vim"', \ ' \ "ftplugin/" . ft . "_*.vim"', \ ' \ "ftplugin/" . ft . "/*.vim"', \ ' if has("nvim")', \ ' execute "runtime! ftplugin/" . ft . ".lua"', \ ' \ "ftplugin/" . ft . "_*.lua"', \ ' \ "ftplugin/" . ft . "/*.lua"', \ ' endif', \ ' endfor', \ ' endif', \ ' call s:after_ftplugin()', \ 'endfunction', \ '', \] endfunction function! s:generate_ftplugin() abort if empty(g:dein#_ftplugin) return endif " Create after/ftplugin let after = dein#util#_get_runtime_path() . '/after/ftplugin' call dein#util#_safe_mkdir(after) " Merge g:dein#_ftplugin let ftplugin = {} for [key, string] in items(g:dein#_ftplugin) for ft in (key ==# '_' ? ['_'] : split(key, '_')) if !has_key(ftplugin, ft) let ftplugin[ft] = (ft ==# '_') ? [] : [ \ "if exists('b:undo_ftplugin')", \ " let b:undo_ftplugin .= '|'", \ 'else', \ " let b:undo_ftplugin = ''", \ 'endif', \ ] endif let ftplugin[ft] += split(string, '\n') endfor endfor " Generate ftplugin.vim call dein#util#_safe_writefile( \ dein#install#_get_default_ftplugin() + [ \ 'function! s:after_ftplugin()', \ ] + get(ftplugin, '_', []) + ['endfunction'], \ dein#util#_get_runtime_path() . '/ftplugin.vim') " Generate after/ftplugin for [filetype, list] in filter(items(ftplugin), \ { _, val -> val[0] !=# '_' }) call dein#util#_safe_writefile( \ list, printf('%s/%s.vim', after, filetype)) endfor endfunction function! dein#install#_is_async() abort return g:dein#install_max_processes > 1 endfunction function! dein#install#_polling() abort if exists('+guioptions') " Note: guioptions-! does not work in async state let save_guioptions = &guioptions set guioptions-=! endif call s:install_async(s:global_context) if exists('+guioptions') let &guioptions = save_guioptions endif endfunction function! dein#install#_remote_plugins() abort if !has('nvim') || g:dein#_is_sudo return endif if has('vim_starting') " Note: UpdateRemotePlugins is not defined in vim_starting autocmd dein VimEnter * silent call dein#remote_plugins() return endif if exists(':UpdateRemotePlugins') != 2 return endif " Load not loaded neovim remote plugins let remote_plugins = filter(values(dein#get()), \ { _, val -> isdirectory(val.rtp . '/rplugin') && !val.sourced }) call dein#autoload#_source(remote_plugins) call s:log('loaded remote plugins: ' . \ string(map(copy(remote_plugins), { _, val -> val.name }))) let &runtimepath = dein#util#_join_rtp(dein#util#_uniq( \ dein#util#_split_rtp(&runtimepath)), &runtimepath, '') let result = execute('UpdateRemotePlugins', '') call s:log(result) endfunction function! dein#install#_each(cmd, plugins) abort let plugins = filter(dein#util#_get_plugins(a:plugins), \ { _, val -> isdirectory(val.path) }) let global_context_save = s:global_context let context = s:init_context(plugins, 'each', 0) call s:init_variables(context) let cwd = getcwd() let error = 0 try for plugin in plugins call dein#install#_cd(plugin.path) if dein#install#_execute(a:cmd) let error = 1 endif endfor catch call s:error(v:exception . ' ' . v:throwpoint) return 1 finally let s:global_context = global_context_save call dein#install#_cd(cwd) endtry return error endfunction function! dein#install#_build(plugins) abort let error = 0 for plugin in filter(dein#util#_get_plugins(a:plugins), \ { _, val -> isdirectory(val.path) && has_key(val, 'build') }) call s:print_progress_message('Building: ' . plugin.name) if dein#install#_each(plugin.build, plugin) let error = 1 endif endfor return error endfunction function! dein#install#_get_log() abort return s:log endfunction function! dein#install#_get_updates_log() abort return s:updates_log endfunction function! dein#install#_get_context() abort return s:global_context endfunction function! dein#install#_get_progress() abort return s:progress endfunction function! s:get_progress_message(name, number, max) abort return printf('(%'.len(a:max).'d/%'.len(a:max).'d) [%s%s] %s', \ a:number, a:max, \ repeat('+', (a:number*20/a:max)), \ repeat('-', 20 - (a:number*20/a:max)), \ a:name) endfunction function! s:get_plugin_message(plugin, number, max, message) abort return printf('(%'.len(a:max).'d/%d) |%-20s| %s', \ a:number, a:max, a:plugin.name, a:message) endfunction function! s:get_short_message(plugin, number, max, message) abort return printf('(%'.len(a:max).'d/%d) %s', a:number, a:max, a:message) endfunction function! s:get_sync_command(plugin, update_type, number, max) abort "{{{i let type = dein#util#_get_type(a:plugin.type) if has_key(type, 'get_sync_command') let cmd = type.get_sync_command(a:plugin) else return ['', ''] endif if empty(cmd) return ['', ''] endif let message = s:get_plugin_message(a:plugin, a:number, a:max, string(cmd)) return [cmd, message] endfunction function! s:get_revision_number(plugin) abort if !isdirectory(a:plugin.path) return '' endif let type = dein#util#_get_type(a:plugin.type) if has_key(type, 'get_revision_number') return type.get_revision_number(a:plugin) endif if !has_key(type, 'get_revision_number_command') return '' endif let cmd = type.get_revision_number_command(a:plugin) if empty(cmd) return '' endif let rev = s:system_cd(cmd, a:plugin.path) " If rev contains spaces, it is error message if rev =~# '\s' call s:error(a:plugin.name) call s:error('Error revision number: ' . rev) return '' elseif rev ==# '' call s:error(a:plugin.name) call s:error('Empty revision number: ' . rev) return '' endif return rev endfunction function! s:get_updated_log_message(plugin, new_rev, old_rev) abort let type = dein#util#_get_type(a:plugin.type) let cmd = has_key(type, 'get_log_command') ? \ type.get_log_command(a:plugin, a:new_rev, a:old_rev) : '' let log = empty(cmd) ? '' : s:system_cd(cmd, a:plugin.path) return log !=# '' ? log : \ (a:old_rev == a:new_rev) ? '' \ : printf('%s -> %s', a:old_rev, a:new_rev) endfunction function! s:lock_revision(process, context) abort let num = a:process.number let max = a:context.max_plugins let plugin = a:process.plugin let type = dein#util#_get_type(plugin.type) if !has_key(type, 'get_revision_lock_command') return 0 endif let cmd = type.get_revision_lock_command(plugin) if empty(cmd) " Skipped. return 0 elseif type(cmd) == v:t_string && cmd =~# '^E: ' " Errored. call s:error(plugin.path) call s:error(cmd[3:]) return -1 endif if get(plugin, 'rev', '') !=# '' call s:log(s:get_plugin_message(plugin, num, max, 'Locked')) endif let result = s:system_cd(cmd, plugin.path) let status = dein#install#_status() if status call s:error(plugin.path) call s:error(result) return -1 endif endfunction function! s:get_updated_message(context, plugins) abort if empty(a:plugins) return '' endif " Diff check if g:dein#install_check_diff call s:check_diff(a:plugins) endif return "Updated plugins:\n". \ join(map(copy(a:plugins), \ { _, val -> ' ' . val.name . (val.commit_count == 0 ? '' \ : printf('(%d change%s)', \ val.commit_count, \ (val.commit_count == 1 ? '' : 's'))) \ . ((val.old_rev !=# '' \ && val.uri =~# '^\h\w*://github.com/') ? "\n" \ . printf(' %s/compare/%s...%s', \ substitute(substitute(val.uri, '\.git$', '', ''), \ '^\h\w*:', 'https:', ''), \ val.old_rev, val.new_rev) : '') \ }) , "\n") endfunction function! s:get_errored_message(plugins) abort if empty(a:plugins) return '' endif let msg = "Error installing plugins:\n".join( \ map(copy(a:plugins), { _, val -> ' ' . val.name }), "\n") let msg .= "\n" let msg .= "Please read the error message log with the :message command.\n" return msg endfunction function! s:check_diff(plugins) abort for plugin in a:plugins let type = dein#util#_get_type(plugin.type) if !has_key(type, 'get_diff_command') || plugin.old_rev ==# '' continue endif let diff = s:system_cd( \ type.get_diff_command(plugin, plugin.old_rev, plugin.new_rev), \ plugin.path) if diff !=# '' echo printf("%s: The documentation is updated\n%s\n\n", \ plugin.name, diff) endif endfor endfunction " Helper functions function! dein#install#_cd(path) abort if !isdirectory(a:path) return endif try noautocmd execute (haslocaldir() ? 'lcd' : 'cd') fnameescape(a:path) catch call s:error('Error cd to: ' . a:path) call s:error('Current directory: ' . getcwd()) call s:error(v:exception) call s:error(v:throwpoint) endtry endfunction function! dein#install#_system(command) abort return s:job_system.system(a:command) endfunction let s:job_system = {} function! s:job_system.on_out(data) abort let candidates = s:job_system.candidates if empty(candidates) call add(candidates, a:data[0]) else let candidates[-1] .= a:data[0] endif let candidates += a:data[1:] endfunction function! s:job_system.system(cmd) abort let self.candidates = [] let job = s:get_job().start( \ s:convert_args(a:cmd), \ {'on_stdout': self.on_out}) let s:job_system.status = job.wait( \ g:dein#install_process_timeout * 1000) return join(s:job_system.candidates, "\n") endfunction function! dein#install#_status() abort return s:job_system.status endfunction function! s:system_cd(command, path) abort let cwd = getcwd() try call dein#install#_cd(a:path) return dein#install#_system(a:command) finally call dein#install#_cd(cwd) endtry return '' endfunction function! dein#install#_execute(command) abort return s:job_execute.execute(a:command) endfunction let s:job_execute = {} function! s:job_execute.on_out(data) abort for line in a:data echo line endfor let candidates = s:job_execute.candidates if empty(candidates) call add(candidates, a:data[0]) else let candidates[-1] .= a:data[0] endif let candidates += a:data[1:] endfunction function! s:job_execute.execute(cmd) abort let self.candidates = [] let job = s:get_job().start( \ s:convert_args(a:cmd), \ {'on_stdout': self.on_out}) return job.wait(g:dein#install_process_timeout * 1000) endfunction function! dein#install#_system_bg(command) abort let job = s:get_job().start( \ s:convert_args(a:command), \ { \ 'on_stderr': { \ v -> map(copy(v), { _, val -> dein#util#_error(val) }) \ } \ }) return job endfunction function! dein#install#_rm(path) abort if !isdirectory(a:path) && !filereadable(a:path) return endif try call delete(a:path, 'rf') catch call s:error('Error deleting directory: ' . a:path) call s:error(v:exception) call s:error(v:throwpoint) endtry endfunction function! dein#install#_copy_directories(srcs, dest) abort if empty(a:srcs) return 0 endif if dein#util#_is_windows() && has('python3') \ && dein#install#_python_version_check() " In Windows, copy directory is too slow! " Note: Python 3.8.0 is needed return dein#install#_copy_directories_py(a:srcs, a:dest) endif let status = 0 if dein#util#_is_windows() if !executable('robocopy') call dein#util#_error('robocopy command is needed.') return 1 endif let status = dein#install#_copy_directories_robocopy(a:srcs, a:dest) else " Not Windows let srcs = map(filter(copy(a:srcs), \ { _, val -> len(glob(val . '/*', v:true, v:true)) }), \ { _, val -> shellescape(val . '/') }) let is_rsync = executable('rsync') if is_rsync let cmdline = printf("rsync -a -q --exclude '/.git/' %s %s", \ join(srcs), shellescape(a:dest)) let result = dein#install#_system(cmdline) let status = dein#install#_status() else for src in srcs let cmdline = printf('cp -Ra %s* %s', src, shellescape(a:dest)) let result = dein#install#_system(cmdline) let status = dein#install#_status() if status break endif endfor endif if status call dein#util#_error('copy command failed.') call dein#util#_error(result) call dein#util#_error('cmdline: ' . cmdline) endif endif return status endfunction function! dein#install#_copy_directories_robocopy(srcs, dest) abort let jobs = [] let format = 'robocopy.exe %s /E /NJH /NJS ' \ . '/NDL /NC /NS /MT:8 /XO /XD ".git"' let srcs = a:srcs let MAX_LINES = 8 while !empty(srcs) let temp = tempname() . '.bat' let lines = ['@echo off'] while len(lines) < MAX_LINES && !empty(srcs) let path = substitute(printf('"%s" "%s"', srcs[0], a:dest), \ '/', '\\', 'g') call add(lines, printf(format, path)) let srcs = srcs[1:] endwhile call dein#util#_safe_writefile(lines, temp) let job = dein#install#_system_bg(temp) call add(jobs, { 'commands': lines, 'job': job }) endwhile " Async check let ret = 0 while !empty(jobs) let i = 0 for job in jobs let status = job.job.wait(100) if status == -1 " Next check let i += 1 continue endif " Robocopy returns between 0 and 7 upon success let status = (status > 7) ? status : 0 if status call dein#util#_error('copy command failed.') call dein#util#_error('cmdline: ' . string(job.commands)) let ret = 1 endif call remove(jobs, i) break endfor endwhile return ret endfunction function! dein#install#_copy_directories_py(srcs, dest) abort py3 << EOF import shutil import vim for src in vim.eval('a:srcs'): shutil.copytree(src, vim.eval('a:dest'), dirs_exist_ok=True, ignore=shutil.ignore_patterns('.git')) EOF endfunction function! dein#install#_python_version_check() abort python3 << EOF import vim import sys vim.vars['dein#_python_version_check'] = ( sys.version_info.major, sys.version_info.minor, sys.version_info.micro) >= (3, 8, 0) EOF return get(g:, 'dein#_python_version_check', 0) endfunction function! s:install_blocking(context) abort try while 1 call s:check_loop(a:context) if empty(a:context.processes) \ && a:context.number == a:context.max_plugins break endif endwhile finally call s:done(a:context) endtry return len(a:context.errored_plugins) endfunction function! s:install_async(context) abort if empty(a:context) return endif call s:check_loop(a:context) if empty(a:context.processes) \ && a:context.number == a:context.max_plugins call s:done(a:context) elseif a:context.number != a:context.prev_number \ && a:context.number < len(a:context.plugins) let plugin = a:context.plugins[a:context.number] call s:print_progress_message( \ s:get_progress_message(plugin.name, \ a:context.number, a:context.max_plugins)) let a:context.prev_number = a:context.number endif return len(a:context.errored_plugins) endfunction function! s:check_loop(context) abort while a:context.number < a:context.max_plugins \ && len(a:context.processes) < g:dein#install_max_processes let plugin = a:context.plugins[a:context.number] call s:sync(plugin, a:context) if !a:context.async call s:print_progress_message( \ s:get_progress_message(plugin.name, \ a:context.number, a:context.max_plugins)) endif endwhile for process in a:context.processes call s:check_output(a:context, process) endfor " Filter eof processes. call filter(a:context.processes, { _, val -> !val.eof }) endfunction function! s:restore_view(context) abort if a:context.progress_type ==# 'tabline' let &g:showtabline = a:context.showtabline let &g:tabline = a:context.tabline elseif a:context.progress_type ==# 'title' let &g:title = a:context.title let &g:titlestring = a:context.titlestring endif endfunction function! s:init_context(plugins, update_type, async) abort let context = {} let context.update_type = a:update_type let context.async = a:async let context.synced_plugins = [] let context.errored_plugins = [] let context.processes = [] let context.number = 0 let context.prev_number = -1 let context.plugins = a:plugins let context.max_plugins = len(context.plugins) let context.progress_type = (has('vim_starting') \ && g:dein#install_progress_type !=# 'none') ? \ 'echo' : g:dein#install_progress_type if !has('nvim') && context.progress_type ==# 'title' let context.progress_type = 'echo' endif let context.message_type = (has('vim_starting') \ && g:dein#install_message_type !=# 'none') ? \ 'echo' : g:dein#install_message_type let context.laststatus = &g:laststatus let context.showtabline = &g:showtabline let context.tabline = &g:tabline let context.title = &g:title let context.titlestring = &g:titlestring return context endfunction function! s:init_variables(context) abort let s:progress = '' let s:global_context = a:context let s:log = [] let s:updates_log = [] endfunction function! s:convert_args(args) abort let args = s:iconv(a:args, &encoding, 'char') if type(args) != v:t_list let args = split(&shell) + split(&shellcmdflag) + [args] endif return args endfunction function! s:start() abort call s:notify(strftime('Update started: (%Y/%m/%d %H:%M:%S)')) endfunction function! s:done(context) abort call s:restore_view(a:context) if !has('vim_starting') call s:notify(s:get_updated_message(a:context, a:context.synced_plugins)) call s:notify(s:get_errored_message(a:context.errored_plugins)) endif if !empty(a:context.synced_plugins) call dein#install#_recache_runtimepath() call dein#source(map(copy(a:context.synced_plugins), \ { _, val -> val.name })) " Execute done_update hooks let done_update_plugins = filter(copy(a:context.synced_plugins), \ { _, val -> has_key(val, 'hook_done_update') }) if !empty(done_update_plugins) if has('vim_starting') let s:done_updated_plugins = done_update_plugins autocmd dein VimEnter * call s:call_done_update_hooks( \ s:done_updated_plugins) else " Reload plugins to execute hooks runtime! plugin/**/*.vim if has('nvim') " Neovim loads lua files at startup runtime! plugin/**/*.lua endif call s:call_done_update_hooks(done_update_plugins) endif endif endif redraw | echo '' call s:notify(strftime('Done: (%Y/%m/%d %H:%M:%S)')) " Disable installation handler let s:global_context = {} let s:progress = '' augroup dein-install autocmd! augroup END if exists('s:timer') call timer_stop(s:timer) unlet s:timer endif endfunction function! s:call_done_update_hooks(plugins) abort let cwd = getcwd() try for plugin in a:plugins call dein#install#_cd(plugin.path) call dein#call_hook('done_update', plugin) endfor finally call dein#install#_cd(cwd) endtry endfunction function! s:sync(plugin, context) abort let a:context.number += 1 let num = a:context.number let max = a:context.max_plugins if isdirectory(a:plugin.path) && get(a:plugin, 'frozen', 0) " Skip frozen plugin call s:log(s:get_plugin_message(a:plugin, num, max, 'is frozen.')) return endif let [cmd, message] = s:get_sync_command( \ a:plugin, a:context.update_type, \ a:context.number, a:context.max_plugins) if empty(cmd) " Skip call s:log(s:get_plugin_message(a:plugin, num, max, message)) return endif if type(cmd) == v:t_string && cmd =~# '^E: ' " Errored. call s:print_progress_message(s:get_plugin_message( \ a:plugin, num, max, 'Error')) call s:error(cmd[3:]) call add(a:context.errored_plugins, \ a:plugin) return endif if !a:context.async call s:print_progress_message(message) endif let process = s:init_process(a:plugin, a:context, cmd) if !empty(process) call add(a:context.processes, process) endif endfunction function! s:init_process(plugin, context, cmd) abort let process = {} let cwd = getcwd() let lang_save = $LANG let prompt_save = $GIT_TERMINAL_PROMPT try let $LANG = 'C' " Disable git prompt (git version >= 2.3.0) let $GIT_TERMINAL_PROMPT = 0 call dein#install#_cd(a:plugin.path) let rev = s:get_revision_number(a:plugin) let process = { \ 'number': a:context.number, \ 'max_plugins': a:context.max_plugins, \ 'rev': rev, \ 'plugin': a:plugin, \ 'output': '', \ 'status': -1, \ 'eof': 0, \ 'installed': isdirectory(a:plugin.path), \ } let rev_save = get(a:plugin, 'rev', '') if isdirectory(a:plugin.path) \ && !get(a:plugin, 'local', 0) \ && rev_save !=# '' try " Force checkout HEAD revision. " The repository may be checked out. let a:plugin.rev = '' call s:lock_revision(process, a:context) finally let a:plugin.rev = rev_save endtry endif call s:init_job(process, a:context, a:cmd) finally let $LANG = lang_save let $GIT_TERMINAL_PROMPT = prompt_save call dein#install#_cd(cwd) endtry return process endfunction function! s:init_job(process, context, cmd) abort let a:process.start_time = localtime() if !a:context.async let a:process.output = dein#install#_system(a:cmd) let a:process.status = dein#install#_status() return endif let a:process.async = {'eof': 0} function! a:process.async.job_handler(data) abort if !has_key(self, 'candidates') let self.candidates = [] endif let candidates = self.candidates if empty(candidates) call add(candidates, a:data[0]) else let candidates[-1] .= a:data[0] endif call s:print_progress_message(candidates[-1]) let candidates += a:data[1:] endfunction function! a:process.async.on_exit(exitval) abort let self.exitval = a:exitval endfunction function! a:process.async.get(process) abort " Check job status let status = -1 if has_key(a:process.job, 'exitval') let self.eof = 1 let status = a:process.job.exitval endif let candidates = get(a:process.job, 'candidates', []) let output = join((self.eof ? candidates : candidates[: -2]), "\n") if output !=# '' && a:process.output !=# output let a:process.output = output let a:process.start_time = localtime() endif let self.candidates = self.eof ? [] : candidates[-1:] let is_timeout = (localtime() - a:process.start_time) \ >= get(a:process.plugin, 'timeout', \ g:dein#install_process_timeout) if self.eof let is_timeout = 0 let is_skip = 0 else let is_skip = 1 endif if is_timeout call a:process.job.stop() let status = -1 endif return [is_timeout, is_skip, status] endfunction let a:process.job = s:get_job().start( \ s:convert_args(a:cmd), { \ 'on_stdout': a:process.async.job_handler, \ 'on_stderr': a:process.async.job_handler, \ 'on_exit': a:process.async.on_exit, \ }) let a:process.id = a:process.job.pid() let a:process.job.candidates = [] endfunction function! s:check_output(context, process) abort if a:context.async let [is_timeout, is_skip, status] = a:process.async.get(a:process) else let [is_timeout, is_skip, status] = [0, 0, a:process.status] endif if is_skip && !is_timeout return endif let num = a:process.number let max = a:context.max_plugins let plugin = a:process.plugin if isdirectory(plugin.path) \ && get(plugin, 'rev', '') !=# '' \ && !get(plugin, 'local', 0) " Restore revision. call s:lock_revision(a:process, a:context) endif let new_rev = s:get_revision_number(plugin) if is_timeout || status call s:log(s:get_plugin_message(plugin, num, max, 'Error')) call s:error(plugin.path) if !a:process.installed if !isdirectory(plugin.path) call s:error('Maybe wrong username or repository.') elseif isdirectory(plugin.path) call s:error('Remove the installed directory:' . plugin.path) call dein#install#_rm(plugin.path) endif endif call s:error((is_timeout ? \ strftime('Process timeout: (%Y/%m/%d %H:%M:%S)') : \ split(a:process.output, '\n') \ )) call add(a:context.errored_plugins, \ plugin) elseif a:process.rev ==# new_rev call s:log(s:get_plugin_message( \ plugin, num, max, 'Same revision')) else call s:log(s:get_plugin_message(plugin, num, max, 'Updated')) let log_messages = split(s:get_updated_log_message( \ plugin, new_rev, a:process.rev), '\n') let plugin.commit_count = len(log_messages) call s:log(map(log_messages, \ { _, val -> s:get_short_message(plugin, num, max, val) })) let plugin.old_rev = a:process.rev let plugin.new_rev = new_rev " Execute "post_update" before "build" if has_key(plugin, 'hook_post_update') " To load plugin is needed to execute "post_update" call dein#source(plugin.name) call dein#call_hook('post_update', plugin) endif let type = dein#util#_get_type(plugin.type) let plugin.uri = has_key(type, 'get_uri') ? \ type.get_uri(plugin.repo, plugin) : '' if dein#install#_build([plugin.name]) call s:log(s:get_plugin_message(plugin, num, max, 'Build failed')) call s:error(plugin.path) " Remove. call add(a:context.errored_plugins, plugin) else call add(a:context.synced_plugins, plugin) endif endif let a:process.eof = 1 endfunction function! s:iconv(expr, from, to) abort if a:from ==# '' || a:to ==# '' || a:from ==? a:to return a:expr endif if type(a:expr) == v:t_list return map(copy(a:expr), { _, val -> iconv(val, a:from, a:to) }) else let result = iconv(a:expr, a:from, a:to) return result !=# '' ? result : a:expr endif endfunction function! s:print_progress_message(msg) abort let msg = dein#util#_convert2list(a:msg) let context = s:global_context if empty(msg) || empty(context) return endif let progress_type = context.progress_type if progress_type ==# 'tabline' set showtabline=2 let &g:tabline = join(msg, "\n") elseif progress_type ==# 'title' set title let &g:titlestring = join(msg, "\n") elseif progress_type ==# 'echo' call s:echo(msg, 'echo') endif call s:log(msg) let s:progress = join(msg, "\n") endfunction function! s:error(msg) abort let msg = dein#util#_convert2list(a:msg) if empty(msg) return endif call s:echo(msg, 'error') call s:updates_log(msg) endfunction function! s:notify(msg) abort let msg = dein#util#_convert2list(a:msg) let context = s:global_context if empty(msg) || empty(context) return endif if context.message_type ==# 'echo' call dein#util#_notify(a:msg) endif call s:updates_log(msg) let s:progress = join(msg, "\n") endfunction function! s:updates_log(msg) abort let msg = dein#util#_convert2list(a:msg) let s:updates_log += msg call s:log(msg) endfunction function! s:log(msg) abort let msg = dein#util#_convert2list(a:msg) let s:log += msg call s:append_log_file(msg) endfunction function! s:append_log_file(msg) abort let logfile = dein#util#_expand(g:dein#install_log_filename) if logfile ==# '' return endif let msg = a:msg " Appends to log file. if filereadable(logfile) let msg = readfile(logfile) + msg endif call dein#util#_safe_writefile(msg, logfile) endfunction function! s:echo(expr, mode) abort let msg = map(filter(dein#util#_convert2list(a:expr), \ { _, val -> val !=# '' }), { _, val -> '[dein] ' . val }) if empty(msg) return endif let more_save = &more let showcmd_save = &showcmd let ruler_save = &ruler try set nomore set noshowcmd set noruler let height = max([1, &cmdheight]) echo '' for i in range(0, len(msg)-1, height) redraw let m = join(msg[i : i+height-1], "\n") call s:echo_mode(m, a:mode) if has('vim_starting') echo '' endif endfor finally let &more = more_save let &showcmd = showcmd_save let &ruler = ruler_save endtry endfunction function! s:echo_mode(m, mode) abort for m in split(a:m, '\r\?\n', 1) if !has('vim_starting') && a:mode !=# 'error' let m = s:truncate_skipping(m, &columns - 1, &columns/3, '...') endif if a:mode ==# 'error' echohl WarningMsg | echomsg m | echohl None elseif a:mode ==# 'echomsg' echomsg m else echo m endif endfor endfunction function! s:truncate_skipping(str, max, footer_width, separator) abort let width = strwidth(a:str) if width <= a:max let ret = a:str else let header_width = a:max - strwidth(a:separator) - a:footer_width let ret = s:strwidthpart(a:str, header_width) . a:separator \ . s:strwidthpart_reverse(a:str, a:footer_width) endif return ret endfunction function! s:strwidthpart(str, width) abort if a:width <= 0 return '' endif let ret = a:str let width = strwidth(a:str) while width > a:width let char = matchstr(ret, '.$') let ret = ret[: -1 - len(char)] let width -= strwidth(char) endwhile return ret endfunction function! s:strwidthpart_reverse(str, width) abort if a:width <= 0 return '' endif let ret = a:str let width = strwidth(a:str) while width > a:width let char = matchstr(ret, '^.') let ret = ret[len(char) :] let width -= strwidth(char) endwhile return ret endfunction