1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-02-03 06:50:05 +08:00
SpaceVim/bundle/dein.vim/autoload/dein/install.vim
2022-06-15 23:59:27 +08:00

1846 lines
50 KiB
VimL

" Variables
let s:global_context = {}
let s:log = []
let s:updates_log = []
let s:progress = ''
let s:failed_plugins = []
let s:progress_winid = -1
" 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)
let g:dein#install_check_remote_threshold =
\ get(g:, 'dein#install_check_remote_threshold', 0)
let g:dein#install_copy_vim =
\ get(g:, 'dein#install_copy_vim',
\ has('nvim') && !dein#util#_is_windows())
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#_get_updated_plugins(plugins, 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 context = s:init_context(a:plugins, 'check_update', 0)
call s:init_variables(context)
let query_max = 100
let plugins = dein#util#_get_plugins(a:plugins)
let processes = []
for index in range(0, len(plugins) - 1, query_max)
call s:print_progress_message(
\ s:get_progress_message('send query', 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 = []
let index = 1
for plugin in plugins
if !has_key(check_pushed, plugin.repo)
let index += 1
continue
endif
call s:print_progress_message(
\ s:get_progress_message('compare plugin', index, len(plugins)))
let git_path = plugin.path . '/.git'
let repo_time = isdirectory(plugin.path) ? getftime(git_path) : -1
call s:log(printf('%s: pushed_time=%d, repo_time=%d, rollback_time=%d',
\ plugin.name, check_pushed[plugin.repo], repo_time, rollback_time))
let local_update = min([repo_time, rollback_time])
if local_update < check_pushed[plugin.repo]
call add(updated, plugin)
elseif abs(local_update - check_pushed[plugin.repo]) <
\ g:dein#install_check_remote_threshold
" Note: github Graph QL API may use cached value
" If the repository is updated recently, use "git ls-remote" instead.
let remote = matchstr(s:system_cd(
\ ['git', 'ls-remote', 'origin', 'HEAD'], plugin.path), '^\x\+')
let local = s:get_revision_number(plugin)
call s:log(printf('%s: remote=%s, local=%s',
\ plugin.name, remote, local))
if remote !=# '' && local !=# remote
call add(updated, plugin)
endif
endif
let index += 1
endfor
redraw | echo ''
if s:progress_winid > 0
call timer_start(1000, { -> s:close_progress_popup() })
endif
" Clear global context
let s:global_context = {}
return updated
endfunction
function! dein#install#_check_update(plugins, force, async) abort
let updated = dein#install#_get_updated_plugins(a:plugins, a:async)
if empty(updated)
call s:notify(strftime('Done: (%Y/%m/%d %H:%M:%S)'))
return
endif
let updated_msg = 'Updated plugins: ' .
\ string(map(copy(updated), { _, val -> val.name }))
call s:log(updated_msg)
" Note: Use echomsg to display it in confirm
call s:echo(updated_msg, 'echomsg')
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')
if get(g:, 'dein#auto_remote_plugins', v:true)
silent call dein#remote_plugins()
endif
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 && !get(val, 'local', v:false) }), '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 vimfiles = []
let luafiles = []
for plugin in a:plugins
for file in filter(globpath(
\ plugin.rtp, a:directory.'/**/*', v:true, v:true),
\ { _, val -> !isdirectory(val) })
if fnamemodify(file, ':e') ==# 'vim'
let vimfiles += readfile(file, ':t')
elseif fnamemodify(file, ':e') ==# 'lua'
let luafiles += readfile(file, ':t')
endif
endfor
endfor
if !empty(vimfiles)
call dein#util#_cache_writefile(vimfiles,
\ printf('.dein/%s/%s.vim', a:directory, a:directory))
endif
if !empty(luafiles)
call dein#util#_cache_writefile(luafiles,
\ printf('.dein/%s/%s.lua', 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_after_ftplugin")',
\ ' finish',
\ 'endif',
\ 'let g:did_load_after_ftplugin = 1',
\ '',
\ 'augroup filetypeplugin',
\ ' autocmd!',
\ ' 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("<amatch>")',
\ ' 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() . '/after/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 &&
\ !empty(glob(val.rtp . '/rplugin/*/*/__init__.py', 1, 1))
\ })
if empty(remote_plugins)
return
endif
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! dein#install#_get_failed_plugins() abort
return s:failed_plugins
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
" Note: execute diff command in background
let cmd = type.get_diff_command(plugin, plugin.old_rev, plugin.new_rev)
let cwd = getcwd()
try
call dein#install#_cd(plugin.path)
call s:get_job().start(
\ s:convert_args(cmd), {
\ 'on_stdout': function('s:check_diff_on_out')
\ })
finally
call dein#install#_cd(cwd)
endtry
endfor
endfunction
function! s:check_diff_on_out(data) abort
let bufname = 'dein-diff'
if !bufexists(bufname)
let bufnr = bufadd(bufname)
else
let bufnr = bufnr(bufname)
endif
if bufwinnr(bufnr) < 0
let cmd = 'setlocal bufhidden=wipe filetype=diff buftype=nofile nolist'
\ . '| syntax enable'
execute printf('sbuffer +%s', escape(cmd, ' ')) bufnr
endif
let current = getbufline(bufnr, '$')[0]
call setbufline(bufnr, '$', current . a:data[0])
call appendbufline(bufnr, '$', a:data[1:])
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 g:dein#install_copy_vim
" Note: For neovim, vim.loop.fs_{sym}link is faster
return dein#install#_copy_directories_vim(a:srcs, a:dest)
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! dein#install#_copy_directories_vim(srcs, dest) abort
for src in a:srcs
for srcpath in glob(src . '/**/*', 1, 1)
let destpath = substitute(srcpath,
\ dein#util#escape_match(src),
\ dein#util#escape_match(a:dest), '')
let parent = fnamemodify(destpath, ':p:h')
if !isdirectory(parent)
call mkdir(parent, 'p')
endif
if isdirectory(srcpath)
call mkdir(destpath, 'p')
elseif srcpath !~# 'tags\%(-\w*\)\?$'
" Ignore tags
call dein#install#_copy_file_vim(srcpath, destpath)
endif
endfor
endfor
endfunction
function! dein#install#_copy_file_vim(src, dest) abort
" Note: In Windows, v:lua.vim.loop.fs_symlink does not work.
if has('nvim')
if dein#util#_is_windows()
call v:lua.vim.loop.fs_link(a:src, a:dest)
else
call v:lua.vim.loop.fs_symlink(a:src, a:dest)
endif
else
let raw = readfile(a:src, 'b')
call writefile(raw, a:dest, 'b')
endif
endfunction
function! dein#install#_deno_cache(...) abort
if !executable('deno')
return
endif
let plugins = dein#util#_get_plugins(get(a:000, 0, []))
for plugin in plugins
if !isdirectory(plugin.rtp . '/denops')
continue
endif
call dein#install#_system(
\ ['deno', 'cache', '--no-check'] +
\ glob(plugin.rtp . '/denops/**/*.ts', 1, 1))
endfor
endfunction
function! dein#install#_post_sync(plugins) abort
if empty(a:plugins)
return
endif
call dein#install#_recache_runtimepath()
call dein#install#_deno_cache(a:plugins)
call dein#source(a:plugins)
" Execute done_update hooks
let done_update_plugins = filter(dein#util#_get_plugins(a: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
call s:call_done_update_hooks(done_update_plugins)
endif
endif
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:close_progress_popup() abort
if winbufnr(s:progress_winid) < 0
return
endif
if has('nvim')
silent! call nvim_win_close(s:progress_winid, v:true)
else
silent! call popup_close(s:progress_winid)
endif
let s:progress_winid = -1
endfunction
function! s:done(context) abort
call s:restore_view(a:context)
let s:failed_plugins = map(copy(a:context.errored_plugins),
\ { _, val -> val.name })
if !empty(a:context.synced_plugins)
let names = map(copy(a:context.synced_plugins), { _, val -> val.name })
call dein#install#_post_sync(names)
endif
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
redraw | echo ''
if s:progress_winid > 0
call timer_start(1000, { -> s:close_progress_popup() })
endif
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
redraw
let progress_type = context.progress_type
let lines = join(msg, "\n")
if progress_type ==# 'tabline'
set showtabline=2
let &g:tabline = lines
elseif progress_type ==# 'title'
set title
let &g:titlestring = lines
elseif progress_type ==# 'floating'
if s:progress_winid <= 0
let s:progress_winid = s:new_progress_window()
endif
let bufnr = winbufnr(s:progress_winid)
if getbufline(bufnr, 1) ==# ['']
call setbufline(bufnr, 1, msg)
else
call appendbufline(bufnr, '$', msg)
endif
call win_execute(s:progress_winid, "call cursor('$', 0) | redraw")
elseif progress_type ==# 'echo'
call s:echo(msg, 'echo')
endif
call s:log(msg)
let s:progress = lines
endfunction
function! s:new_progress_window() abort
let winrow = 0
let wincol = &columns / 4
let winwidth = 80
let winheight = 20
if has('nvim')
let winid = nvim_open_win(nvim_create_buf(v:false, v:true), v:true, {
\ 'relative': 'editor',
\ 'row': winrow,
\ 'col': wincol,
\ 'focusable': v:false,
\ 'noautocmd': v:true,
\ 'style': 'minimal',
\ 'width': winwidth,
\ 'height': winheight,
\})
else
let winid = popup_create([], {
\ 'pos': 'topleft',
\ 'line': winrow + 1,
\ 'col': wincol + 1,
\ 'minwidth': winwidth,
\ 'minheight': winheight,
\ 'wrap': 0,
\ })
endif
return winid
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