diff --git a/vim/autoload/plug.vim b/vim/autoload/plug.vim index 36b7f71..cabeb4f 100644 --- a/vim/autoload/plug.vim +++ b/vim/autoload/plug.vim @@ -3,8 +3,7 @@ " " Download plug.vim and put it in ~/.vim/autoload " -" mkdir -p ~/.vim/autoload -" curl -fLo ~/.vim/autoload/plug.vim \ +" curl -fLo ~/.vim/autoload/plug.vim --create-dirs \ " https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim " " Edit your .vimrc @@ -15,6 +14,9 @@ " Plug 'junegunn/seoul256.vim' " Plug 'junegunn/vim-easy-align' " +" " Group dependencies, vim-snippets depends on ultisnips +" Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets' +" " " On-demand loading " Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } " Plug 'tpope/vim-fireplace', { 'for': 'clojure' } @@ -23,7 +25,7 @@ " Plug 'https://github.com/junegunn/vim-github-dashboard.git' " " " Plugin options -" Plug 'nsf/gocode', { 'tag': 'go.weekly.2012-03-13', 'rtp': 'vim' } +" Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' } " " " Plugin outside ~/.vim/plugged with post-update hook " Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': 'yes \| ./install' } @@ -37,7 +39,7 @@ " Visit https://github.com/junegunn/vim-plug for more information. " " -" Copyright (c) 2014 Junegunn Choi +" Copyright (c) 2015 Junegunn Choi " " MIT License " @@ -68,12 +70,12 @@ let g:loaded_plug = 1 let s:cpo_save = &cpo set cpo&vim -let s:plug_src = 'https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim' +let s:plug_src = 'https://github.com/junegunn/vim-plug.git' let s:plug_tab = get(s:, 'plug_tab', -1) let s:plug_buf = get(s:, 'plug_buf', -1) let s:mac_gui = has('gui_macvim') && has('gui_running') let s:is_win = has('win32') || has('win64') -let s:nvim = exists('##JobActivity') && !s:is_win +let s:nvim = has('nvim') && exists('*jobwait') && !s:is_win let s:me = resolve(expand(':p')) let s:base_spec = { 'branch': 'master', 'frozen': 0 } let s:TYPE = { @@ -83,6 +85,7 @@ let s:TYPE = { \ 'funcref': type(function('call')) \ } let s:loaded = get(s:, 'loaded', {}) +let s:triggers = get(s:, 'triggers', {}) function! plug#begin(...) if a:0 > 0 @@ -99,6 +102,7 @@ function! plug#begin(...) let g:plug_home = home let g:plugs = {} let g:plugs_order = [] + let s:triggers = {} call s:define_commands() return 1 @@ -112,7 +116,7 @@ function! s:define_commands() command! -nargs=* -bar -bang -complete=customlist,s:names PlugInstall call s:install('' == '!', []) command! -nargs=* -bar -bang -complete=customlist,s:names PlugUpdate call s:update('' == '!', []) command! -nargs=0 -bar -bang PlugClean call s:clean('' == '!') - command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:me | endif + command! -nargs=0 -bar PlugUpgrade if s:upgrade() | execute 'source' s:esc(s:me) | endif command! -nargs=0 -bar PlugStatus call s:status() command! -nargs=0 -bar PlugDiff call s:diff() command! -nargs=? -bar PlugSnapshot call s:snapshot() @@ -122,14 +126,22 @@ function! s:to_a(v) return type(a:v) == s:TYPE.list ? a:v : [a:v] endfunction +function! s:to_s(v) + return type(a:v) == s:TYPE.string ? a:v : join(a:v, "\n") . "\n" +endfunction + function! s:source(from, ...) for pattern in a:000 for vim in s:lines(globpath(a:from, pattern)) - execute 'source' vim + execute 'source' s:esc(vim) endfor endfor endfunction +function! s:assoc(dict, key, val) + let a:dict[a:key] = add(get(a:dict, a:key, []), a:val) +endfunction + function! plug#end() if !exists('g:plugs') return s:err('Call plug#begin() first') @@ -141,7 +153,7 @@ function! plug#end() augroup END augroup! PlugLOD endif - let lod = {} + let lod = { 'ft': {}, 'map': {}, 'cmd': {} } filetype off for name in g:plugs_order @@ -152,20 +164,18 @@ function! plug#end() endif if has_key(plug, 'on') + let s:triggers[name] = { 'map': [], 'cmd': [] } for cmd in s:to_a(plug.on) - if cmd =~ '^.\+' + if cmd =~? '^.\+' if empty(mapcheck(cmd)) && empty(mapcheck(cmd, 'i')) - for [mode, map_prefix, key_prefix] in - \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] - execute printf( - \ '%snoremap %s %s:call lod_map(%s, %s, "%s")', - \ mode, cmd, map_prefix, string(cmd), string(name), key_prefix) - endfor + call s:assoc(lod.map, cmd, name) + endif + call add(s:triggers[name].map, cmd) + elseif cmd =~ '^[A-Z]' + if exists(':'.cmd) != 2 + call s:assoc(lod.cmd, cmd, name) endif - elseif !exists(':'.cmd) - execute printf( - \ 'command! -nargs=* -range -bang %s call s:lod_cmd(%s, "", , , , %s)', - \ cmd, string(cmd), string(name)) + call add(s:triggers[name].cmd, cmd) endif endfor endif @@ -175,19 +185,31 @@ function! plug#end() if !empty(types) call s:source(s:rtp(plug), 'ftdetect/**/*.vim', 'after/ftdetect/**/*.vim') endif - for key in types - if !has_key(lod, key) - let lod[key] = [] - endif - call add(lod[key], name) + for type in types + call s:assoc(lod.ft, type, name) endfor endif endfor - for [key, names] in items(lod) + for [cmd, names] in items(lod.cmd) + execute printf( + \ 'command! -nargs=* -range -bang %s call s:lod_cmd(%s, "", , , , %s)', + \ cmd, string(cmd), string(names)) + endfor + + for [map, names] in items(lod.map) + for [mode, map_prefix, key_prefix] in + \ [['i', '', ''], ['n', '', ''], ['v', '', 'gv'], ['o', '', '']] + execute printf( + \ '%snoremap %s %s:call lod_map(%s, %s, "%s")', + \ mode, map, map_prefix, string(map), string(names), key_prefix) + endfor + endfor + + for [ft, names] in items(lod.ft) augroup PlugLOD execute printf('autocmd FileType %s call lod_ft(%s, %s)', - \ key, string(key), string(names)) + \ ft, string(ft), string(names)) augroup END endfor @@ -214,6 +236,27 @@ function! s:trim(str) return substitute(a:str, '[\/]\+$', '', '') endfunction +function! s:version_requirement(val, min) + for idx in range(0, len(a:min) - 1) + let v = get(a:val, idx, 0) + if v < a:min[idx] | return 0 + elseif v > a:min[idx] | return 1 + endif + endfor + return 1 +endfunction + +function! s:git_version_requirement(...) + let s:git_version = get(s:, 'git_version', + \ map(split(split(s:system('git --version'))[-1], '\.'), 'str2nr(v:val)')) + return s:version_requirement(s:git_version, a:000) +endfunction + +function! s:progress_opt(base) + return a:base && !s:is_win && + \ s:git_version_requirement(1, 7, 1) ? '--progress' : '' +endfunction + if s:is_win function! s:rtp(spec) return s:path(a:spec.dir . get(a:spec, 'rtp', '')) @@ -228,7 +271,7 @@ if s:is_win endfunction function! s:is_local_plug(repo) - return a:repo =~? '^[a-z]:' + return a:repo =~? '^[a-z]:\|^[%~]' endfunction else function! s:rtp(spec) @@ -289,9 +332,10 @@ function! s:reorg_rtp() let s:middle = get(s:, 'middle', &rtp) let rtps = map(s:loaded_names(), 's:rtp(g:plugs[v:val])') let afters = filter(map(copy(rtps), 'globpath(v:val, "after")'), 'isdirectory(v:val)') - let &rtp = join(map(rtps, 's:escrtp(v:val)'), ',') - \ . substitute(','.s:middle.',', '^,,$', ',', '') - \ . join(map(afters, 's:escrtp(v:val)'), ',') + let rtp = join(map(rtps, 'escape(v:val, ",")'), ',') + \ . ','.s:middle.',' + \ . join(map(afters, 'escape(v:val, ",")'), ',') + let &rtp = substitute(substitute(rtp, ',,*', ',', 'g'), '^,\|,$', '', 'g') let s:prtp = &rtp if !empty(s:first_rtp) @@ -315,12 +359,29 @@ function! plug#load(...) for name in a:000 call s:lod([name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) endfor - silent! doautocmd BufRead + if exists('#BufRead') + doautocmd BufRead + endif return 1 endfunction +function! s:remove_triggers(name) + if !has_key(s:triggers, a:name) + return + endif + for cmd in s:triggers[a:name].cmd + execute 'silent! delc' cmd + endfor + for map in s:triggers[a:name].map + execute 'silent! unmap' map + execute 'silent! iunmap' map + endfor + call remove(s:triggers, a:name) +endfunction + function! s:lod(names, types) for name in a:names + call s:remove_triggers(name) let s:loaded[name] = 1 endfor call s:reorg_rtp() @@ -330,26 +391,30 @@ function! s:lod(names, types) for dir in a:types call s:source(rtp, dir.'/**/*.vim') endfor + if exists('#User#'.name) + execute 'doautocmd User' name + endif endfor endfunction function! s:lod_ft(pat, names) call s:lod(a:names, ['plugin', 'after/plugin']) execute 'autocmd! PlugLOD FileType' a:pat - silent! doautocmd filetypeplugin FileType - silent! doautocmd filetypeindent FileType + if exists('#filetypeplugin#FileType') + doautocmd filetypeplugin FileType + endif + if exists('#filetypeindent#FileType') + doautocmd filetypeindent FileType + endif endfunction -function! s:lod_cmd(cmd, bang, l1, l2, args, name) - execute 'delc' a:cmd - call s:lod([a:name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) +function! s:lod_cmd(cmd, bang, l1, l2, args, names) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) execute printf('%s%s%s %s', (a:l1 == a:l2 ? '' : (a:l1.','.a:l2)), a:cmd, a:bang, a:args) endfunction -function! s:lod_map(map, name, prefix) - execute 'unmap' a:map - execute 'iunmap' a:map - call s:lod([a:name], ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) +function! s:lod_map(map, names, prefix) + call s:lod(a:names, ['ftdetect', 'after/ftdetect', 'plugin', 'after/plugin']) let extra = '' while 1 let c = getchar(0) @@ -371,9 +436,11 @@ function! s:add(repo, ...) let name = fnamemodify(repo, ':t:s?\.git$??') let spec = extend(s:infer_properties(name, repo), \ a:0 == 1 ? s:parse_options(a:1) : s:base_spec) + if !has_key(g:plugs, name) + call add(g:plugs_order, name) + endif let g:plugs[name] = spec - let g:plugs_order += [name] - let s:loaded[name] = 0 + let s:loaded[name] = get(s:loaded, name, 0) catch return s:err(v:exception) endtry @@ -383,12 +450,9 @@ function! s:parse_options(arg) let opts = copy(s:base_spec) let type = type(a:arg) if type == s:TYPE.string - let opts.branch = a:arg + let opts.tag = a:arg elseif type == s:TYPE.dict call extend(opts, a:arg) - if has_key(opts, 'tag') - let opts.branch = remove(opts, 'tag') - endif if has_key(opts, 'dir') let opts.dir = s:dirpath(expand(opts.dir)) endif @@ -555,6 +619,7 @@ function! s:prepare() endif silent! unmap silent! unmap L + silent! unmap o silent! unmap X setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap cursorline modifiable setf vim-plug @@ -613,6 +678,11 @@ function! s:do(pull, force, todo) endfunction function! s:finish(pull) + let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) + if new_frozen + let s = new_frozen > 1 ? 's' : '' + call append(3, printf('- Installed %d frozen plugin%s', new_frozen, s)) + endif call append(3, '- Finishing ... ') redraw call plug#helptags() @@ -623,7 +693,7 @@ function! s:finish(pull) if !empty(s:update.errors) call add(msgs, "Press 'R' to retry.") endif - if a:pull && !empty(filter(getline(5, '$'), + if a:pull && len(s:update.new) < len(filter(getline(5, '$'), \ "v:val =~ '^- ' && stridx(v:val, 'Already up-to-date') < 0")) call add(msgs, "Press 'D' to see the updated changes.") endif @@ -649,10 +719,10 @@ endfunction function! s:update_impl(pull, force, args) abort let args = copy(a:args) let threads = (len(args) > 0 && args[-1] =~ '^[1-9][0-9]*$') ? - \ remove(args, -1) : get(g:, 'plug_threads', 16) + \ remove(args, -1) : get(g:, 'plug_threads', s:is_win ? 1 : 16) let managed = filter(copy(g:plugs), 's:is_managed(v:key)') - let todo = empty(args) ? filter(managed, '!v:val.frozen') : + let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : \ filter(managed, 'index(args, v:key) >= 0') if empty(todo) @@ -662,15 +732,34 @@ function! s:update_impl(pull, force, args) abort return endif + if !s:is_win && s:git_version_requirement(2, 3) + let s:git_terminal_prompt = exists('$GIT_TERMINAL_PROMPT') ? $GIT_TERMINAL_PROMPT : '' + let $GIT_TERMINAL_PROMPT = 0 + for plug in values(todo) + let plug.uri = substitute(plug.uri, + \ '^https://git::@github\.com', 'https://github.com', '') + endfor + endif + if !isdirectory(g:plug_home) try call mkdir(g:plug_home, 'p') catch - return s:err(printf('Invalid plug directory: %s.' + return s:err(printf('Invalid plug directory: %s. '. \ 'Try to call plug#begin with a valid directory', g:plug_home)) endtry endif + if has('nvim') && !exists('*jobwait') && threads > 1 + echohl WarningMsg + echomsg 'vim-plug: update Neovim for parallel installer' + echohl None + endif + + let python = (has('python') || has('python3')) && !s:is_win && !has('win32unix') + \ && (!s:nvim || has('vim_starting')) + let ruby = has('ruby') && !s:nvim && (v:version >= 703 || v:version == 702 && has('patch374')) + let s:update = { \ 'start': reltime(), \ 'all': todo, @@ -679,7 +768,7 @@ function! s:update_impl(pull, force, args) abort \ 'pull': a:pull, \ 'force': a:force, \ 'new': {}, - \ 'threads': (has('ruby') || s:nvim) ? min([len(todo), threads]) : 1, + \ 'threads': (python || ruby || s:nvim) ? min([len(todo), threads]) : 1, \ 'bar': '', \ 'fin': 0 \ } @@ -687,18 +776,35 @@ function! s:update_impl(pull, force, args) abort call s:prepare() call append(0, ['', '']) normal! 2G + silent! redraw + + let s:clone_opt = get(g:, 'plug_shallow', 1) ? + \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : '' + + " Python version requirement (>= 2.7) + if python && !has('python3') && !ruby && !s:nvim && s:update.threads > 1 + redir => pyv + silent python import platform; print(platform.python_version()) + redir END + let python = s:version_requirement( + \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6]) + endif - if has('ruby') && s:update.threads > 1 + if (python || ruby) && s:update.threads > 1 try let imd = &imd if s:mac_gui set noimd endif - call s:update_ruby() + if ruby + call s:update_ruby() + else + call s:update_python() + endif catch let lines = getline(4, '$') let printed = {} - silent 4,$d _ + silent! 4,$d _ for line in lines let name = s:extract_name(line, '.', '') if empty(name) || !has_key(printed, name) @@ -721,6 +827,9 @@ function! s:update_impl(pull, force, args) abort endfunction function! s:update_finish() + if exists('s:git_terminal_prompt') + let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt + endif if s:switch_in() call s:do(s:update.pull, s:update.force, filter(copy(s:update.all), 'has_key(v:val, "do")')) call s:finish(s:update.pull) @@ -733,67 +842,58 @@ function! s:job_abort() if !s:nvim || !exists('s:jobs') return endif - augroup PlugJobControl - autocmd! - augroup END + for [name, j] in items(s:jobs) silent! call jobstop(j.jobid) if j.new - call system('rm -rf ' . s:shellesc(g:plugs[name].dir)) + call s:system('rm -rf ' . s:shellesc(g:plugs[name].dir)) endif endfor - let s:jobs = {} - let s:jobs_idx = {} + let s:jobs = {} endfunction -function! s:job_handler() abort +" When a:event == 'stdout', data = list of strings +" When a:event == 'exit', data = returncode +function! s:job_handler(job_id, data, event) abort if !s:plug_window_exists() " plug window closed return s:job_abort() endif - let name = get(s:jobs_idx, v:job_data[0], '') - if empty(name) " stale task - return - endif - let job = s:jobs[name] - - if v:job_data[1] == 'exit' - let job.running = 0 - if s:lastline(job.result) ==# 'Error' - let job.error = 1 - let job.result = substitute(job.result, "Error[\r\n]$", '', '') - endif - call s:reap(name) - call s:tick() - else - let job.result .= v:job_data[2] + if a:event == 'stdout' + let self.result .= substitute(s:to_s(a:data), '[\r\n]', '', 'g') . "\n" " To reduce the number of buffer updates - let job.tick = get(job, 'tick', -1) + 1 - if job.tick % len(s:jobs) == 0 - call s:log(job.new ? '+' : '*', name, job.result) + let self.tick = get(self, 'tick', -1) + 1 + if self.tick % len(s:jobs) == 0 + call s:log(self.new ? '+' : '*', self.name, self.result) endif + elseif a:event == 'exit' + let self.running = 0 + if a:data != 0 + let self.error = 1 + endif + call s:reap(self.name) + call s:tick() endif endfunction function! s:spawn(name, cmd, opts) - let job = { 'running': 1, 'new': get(a:opts, 'new', 0), - \ 'error': 0, 'result': '' } + let job = { 'name': a:name, 'running': 1, 'error': 0, 'result': '', + \ 'new': get(a:opts, 'new', 0), + \ 'on_stdout': function('s:job_handler'), + \ 'on_exit' : function('s:job_handler'), + \ } let s:jobs[a:name] = job if s:nvim - let x = jobstart(a:name, 'sh', ['-c', - \ (has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd) - \ . ' || echo Error']) - if x > 0 - let s:jobs_idx[x] = a:name - let job.jobid = x - augroup PlugJobControl - execute 'autocmd JobActivity' a:name 'call s:job_handler()' - augroup END + let argv = [ 'sh', '-c', + \ (has_key(a:opts, 'dir') ? s:with_cd(a:cmd, a:opts.dir) : a:cmd) ] + let jid = jobstart(argv, job) + if jid > 0 + let job.jobid = jid else let job.running = 0 let job.error = 1 - let job.result = x < 0 ? 'sh is not executable' : + let job.result = jid < 0 ? 'sh is not executable' : \ 'Invalid arguments (or job table is full)' endif else @@ -805,10 +905,6 @@ function! s:spawn(name, cmd, opts) endfunction function! s:reap(name) - if s:nvim - silent! execute 'autocmd! PlugJobControl JobActivity' a:name - endif - let job = s:jobs[a:name] if job.error call add(s:update.errors, a:name) @@ -859,14 +955,15 @@ function! s:log(bullet, name, lines) endfunction function! s:update_vim() - let s:jobs = {} - let s:jobs_idx = {} + let s:jobs = {} call s:bar() call s:tick() endfunction function! s:tick() + let pull = s:update.pull + let prog = s:progress_opt(s:nvim) while 1 " Without TCO, Vim stack is bound to explode if empty(s:update.todo) if empty(s:jobs) && !s:update.fin @@ -878,19 +975,23 @@ while 1 " Without TCO, Vim stack is bound to explode let name = keys(s:update.todo)[0] let spec = remove(s:update.todo, name) - let pull = s:update.pull let new = !isdirectory(spec.dir) call s:log(new ? '+' : '*', name, pull ? 'Updating ...' : 'Installing ...') redraw + let has_tag = has_key(spec, 'tag') + let checkout = s:shellesc(has_tag ? spec.tag : spec.branch) + let merge = s:shellesc(has_tag ? spec.tag : 'origin/'.spec.branch) + if !new let [valid, msg] = s:git_valid(spec, 0) if valid if pull + let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' call s:spawn(name, - \ printf('git checkout -q %s 2>&1 && git pull --progress --no-rebase origin %s 2>&1 && git submodule update --init --recursive 2>&1', - \ s:shellesc(spec.branch), s:shellesc(spec.branch)), { 'dir': spec.dir }) + \ printf('(git fetch %s %s 2>&1 && git checkout -q %s 2>&1 && git merge --ff-only %s 2>&1 && git submodule update --init --recursive 2>&1)', + \ fetch_opt, prog, checkout, merge), { 'dir': spec.dir }) else let s:jobs[name] = { 'running': 0, 'result': 'Already installed', 'error': 0 } endif @@ -899,9 +1000,11 @@ while 1 " Without TCO, Vim stack is bound to explode endif else call s:spawn(name, - \ printf('git clone --progress --recursive %s -b %s %s 2>&1', + \ printf('git clone %s %s --recursive %s -b %s %s 2>&1', + \ has_tag ? '' : s:clone_opt, + \ prog, \ s:shellesc(spec.uri), - \ s:shellesc(spec.branch), + \ checkout, \ s:shellesc(s:trim(spec.dir))), { 'new': 1 }) endif @@ -914,6 +1017,384 @@ while 1 " Without TCO, Vim stack is bound to explode endwhile endfunction +function! s:update_python() +let py_exe = has('python3') ? 'python3' : 'python' +execute py_exe "<< EOF" +""" Due to use of signals this function is POSIX only. """ +import datetime +import functools +import os +try: + import queue +except ImportError: + import Queue as queue +import random +import re +import shutil +import signal +import subprocess +import tempfile +import threading as thr +import time +import traceback +import vim + +G_NVIM = vim.eval("has('nvim')") == '1' +G_PULL = vim.eval('s:update.pull') == '1' +G_RETRIES = int(vim.eval('get(g:, "plug_retries", 2)')) + 1 +G_TIMEOUT = int(vim.eval('get(g:, "plug_timeout", 60)')) +G_CLONE_OPT = vim.eval('s:clone_opt') +G_PROGRESS = vim.eval('s:progress_opt(1)') +G_LOG_PROB = 1.0 / int(vim.eval('s:update.threads')) +G_STOP = thr.Event() +G_THREADS = {} + +class BaseExc(Exception): + def __init__(self, msg): + self._msg = msg + @property + def msg(self): + return self._msg +class CmdTimedOut(BaseExc): + pass +class CmdFailed(BaseExc): + pass +class InvalidURI(BaseExc): + pass +class Action(object): + INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] + +class Buffer(object): + def __init__(self, lock, num_plugs, is_pull, is_win): + self.bar = '' + self.event = 'Updating' if is_pull else 'Installing' + self.is_win = is_win + self.lock = lock + self.maxy = int(vim.eval('winheight(".")')) + self.num_plugs = num_plugs + + def _where(self, name): + """ Find first line with name in current buffer. Return line num. """ + found, lnum = False, 0 + matcher = re.compile('^[-+x*] {0}:'.format(name)) + for line in vim.current.buffer: + if matcher.search(line) is not None: + found = True + break + lnum += 1 + + if not found: + lnum = -1 + return lnum + + def header(self): + curbuf = vim.current.buffer + curbuf[0] = self.event + ' plugins ({0}/{1})'.format(len(self.bar), self.num_plugs) + + num_spaces = self.num_plugs - len(self.bar) + curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ') + + with self.lock: + vim.command('normal! 2G') + if not self.is_win: + vim.command('redraw') + + def write(self, action, name, lines): + first, rest = lines[0], lines[1:] + msg = ['{0} {1}{2}{3}'.format(action, name, ': ' if first else '', first)] + padded_rest = [' ' + line for line in rest] + msg.extend(padded_rest) + + try: + if action == Action.ERROR: + self.bar += 'x' + vim.command("call add(s:update.errors, '{0}')".format(name)) + elif action == Action.DONE: + self.bar += '=' + + curbuf = vim.current.buffer + lnum = self._where(name) + if lnum != -1: # Found matching line num + del curbuf[lnum] + if lnum > self.maxy and action in set([Action.INSTALL, Action.UPDATE]): + lnum = 3 + else: + lnum = 3 + curbuf.append(msg, lnum) + + self.header() + except vim.error: + pass + +class Command(object): + def __init__(self, cmd, cmd_dir=None, timeout=60, ntries=3, cb=None, clean=None): + self.cmd = cmd + self.cmd_dir = cmd_dir + self.timeout = timeout + self.ntries = ntries + self.callback = cb if cb else (lambda msg: None) + self.clean = clean + + def attempt_cmd(self): + """ Tries to run the command, returns result if no exceptions. """ + attempt = 0 + finished = False + limit = self.timeout + + while not finished: + try: + attempt += 1 + result = self.timeout_cmd() + finished = True + except CmdTimedOut: + if attempt != self.ntries: + for count in range(3, 0, -1): + if G_STOP.is_set(): + raise KeyboardInterrupt + msg = 'Timeout. Will retry in {0} second{1} ...'.format( + count, 's' if count != 1 else '') + self.callback([msg]) + time.sleep(1) + self.timeout += limit + self.callback(['Retrying ...']) + else: + raise + + return result + + def timeout_cmd(self): + """ Execute a cmd & poll for callback. Returns list of output. + Raises CmdFailed -> return code for Popen isn't 0 + Raises CmdTimedOut -> command exceeded timeout without new output + """ + proc = None + first_line = True + try: + tfile = tempfile.NamedTemporaryFile(mode='w+b', delete=False) + proc = subprocess.Popen(self.cmd, cwd=self.cmd_dir, stdout=tfile, + stderr=subprocess.STDOUT, shell=True, preexec_fn=os.setsid) + while proc.poll() is None: + # Yield this thread + time.sleep(0.2) + + if G_STOP.is_set(): + raise KeyboardInterrupt + + if first_line or random.random() < G_LOG_PROB: + first_line = False + line = nonblock_read(tfile.name) + if line: + self.callback([line]) + + time_diff = time.time() - os.path.getmtime(tfile.name) + if time_diff > self.timeout: + raise CmdTimedOut(['Timeout!']) + + tfile.seek(0) + result = [line.decode().rstrip() for line in tfile] + + if proc.returncode != 0: + msg = [''] + msg.extend(result) + raise CmdFailed(msg) + except: + if proc and proc.poll() is None: + os.killpg(proc.pid, signal.SIGTERM) + if self.clean: + self.clean() + raise + finally: + os.remove(tfile.name) + + return result + +class Plugin(object): + def __init__(self, name, args, buf_q, lock): + self.name = name + self.args = args + self.buf_q = buf_q + self.lock = lock + tag = args.get('tag', 0) + self.checkout = esc(tag if tag else args['branch']) + self.merge = esc(tag if tag else 'origin/' + args['branch']) + self.tag = tag + + def manage(self): + try: + if os.path.exists(self.args['dir']): + self.update() + else: + self.install() + with self.lock: + thread_vim_command("let s:update.new['{0}'] = 1".format(self.name)) + except (CmdTimedOut, CmdFailed, InvalidURI) as exc: + self.write(Action.ERROR, self.name, exc.msg) + except KeyboardInterrupt: + G_STOP.set() + self.write(Action.ERROR, self.name, ['Interrupted!']) + except: + # Any exception except those above print stack trace + msg = 'Trace:\n{0}'.format(traceback.format_exc().rstrip()) + self.write(Action.ERROR, self.name, msg.split('\n')) + raise + + def install(self): + target = self.args['dir'] + + def clean(target): + def _clean(): + try: + shutil.rmtree(target) + except OSError: + pass + return _clean + + self.write(Action.INSTALL, self.name, ['Installing ...']) + callback = functools.partial(self.write, Action.INSTALL, self.name) + cmd = 'git clone {0} {1} --recursive {2} -b {3} {4} 2>&1'.format( + '' if self.tag else G_CLONE_OPT, G_PROGRESS, self.args['uri'], self.checkout, esc(target)) + com = Command(cmd, None, G_TIMEOUT, G_RETRIES, callback, clean(target)) + result = com.attempt_cmd() + self.write(Action.DONE, self.name, result[-1:]) + + def update(self): + match = re.compile(r'git::?@') + actual_uri = re.sub(match, '', self.repo_uri()) + expect_uri = re.sub(match, '', self.args['uri']) + if actual_uri != expect_uri: + msg = ['', + 'Invalid URI: {0}'.format(actual_uri), + 'Expected {0}'.format(expect_uri), + 'PlugClean required.'] + raise InvalidURI(msg) + + if G_PULL: + self.write(Action.UPDATE, self.name, ['Updating ...']) + callback = functools.partial(self.write, Action.UPDATE, self.name) + fetch_opt = '--depth 99999999' if self.tag and os.path.isfile(os.path.join(self.args['dir'], '.git/shallow')) else '' + cmds = ['git fetch {0} {1}'.format(fetch_opt, G_PROGRESS), + 'git checkout -q {0}'.format(self.checkout), + 'git merge --ff-only {0}'.format(self.merge), + 'git submodule update --init --recursive'] + cmd = ' 2>&1 && '.join(cmds) + com = Command(cmd, self.args['dir'], G_TIMEOUT, G_RETRIES, callback) + result = com.attempt_cmd() + self.write(Action.DONE, self.name, result[-1:]) + else: + self.write(Action.DONE, self.name, ['Already installed']) + + def repo_uri(self): + cmd = 'git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url' + command = Command(cmd, self.args['dir'], G_TIMEOUT, G_RETRIES) + result = command.attempt_cmd() + return result[-1] + + def write(self, action, name, msg): + self.buf_q.put((action, name, msg)) + +class PlugThread(thr.Thread): + def __init__(self, tname, args): + super(PlugThread, self).__init__() + self.tname = tname + self.args = args + + def run(self): + thr.current_thread().name = self.tname + buf_q, work_q, lock = self.args + + try: + while not G_STOP.is_set(): + name, args = work_q.get_nowait() + plug = Plugin(name, args, buf_q, lock) + plug.manage() + work_q.task_done() + except queue.Empty: + pass + finally: + global G_THREADS + with lock: + del G_THREADS[thr.current_thread().name] + +class RefreshThread(thr.Thread): + def __init__(self, lock): + super(RefreshThread, self).__init__() + self.lock = lock + self.running = True + + def run(self): + while self.running: + with self.lock: + thread_vim_command('noautocmd normal! a') + time.sleep(0.2) + + def stop(self): + self.running = False + +if G_NVIM: + def thread_vim_command(cmd): + vim.session.threadsafe_call(lambda: vim.command(cmd)) +else: + def thread_vim_command(cmd): + vim.command(cmd) + +def esc(name): + return '"' + name.replace('"', '\"') + '"' + +def nonblock_read(fname): + """ Read a file with nonblock flag. Return the last line. """ + fread = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) + buf = os.read(fread, 100000).decode() + os.close(fread) + + line = buf.rstrip('\r\n') + left = max(line.rfind('\r'), line.rfind('\n')) + if left != -1: + left += 1 + line = line[left:] + + return line + +def main(): + thr.current_thread().name = 'main' + nthreads = int(vim.eval('s:update.threads')) + plugs = vim.eval('s:update.todo') + mac_gui = vim.eval('s:mac_gui') == '1' + is_win = vim.eval('s:is_win') == '1' + + lock = thr.Lock() + buf = Buffer(lock, len(plugs), G_PULL, is_win) + buf_q, work_q = queue.Queue(), queue.Queue() + for work in plugs.items(): + work_q.put(work) + + global G_THREADS + for num in range(nthreads): + tname = 'PlugT-{0:02}'.format(num) + thread = PlugThread(tname, (buf_q, work_q, lock)) + thread.start() + G_THREADS[tname] = thread + if mac_gui: + rthread = RefreshThread(lock) + rthread.start() + + while not buf_q.empty() or len(G_THREADS) != 0: + try: + action, name, msg = buf_q.get(True, 0.25) + buf.write(action, name, msg) + buf_q.task_done() + except queue.Empty: + pass + except KeyboardInterrupt: + G_STOP.set() + + if mac_gui: + rthread.stop() + rthread.join() + +main() +EOF +endfunction + function! s:update_ruby() ruby << EOF module PlugStream @@ -1015,7 +1496,7 @@ function! s:update_ruby() if iswin Timeout::timeout(timeout) do tmp = VIM::evaluate('tempname()') - system("#{cmd} > #{tmp}") + system("(#{cmd}) > #{tmp}") data = File.read(tmp).chomp File.unlink tmp rescue nil end @@ -1072,19 +1553,21 @@ function! s:update_ruby() end } if VIM::evaluate('s:mac_gui') == 1 - progress = iswin ? '' : '--progress' + clone_opt = VIM::evaluate('s:clone_opt') + progress = VIM::evaluate('s:progress_opt(1)') nthr.times do mtx.synchronize do threads << Thread.new { while pair = take1.call name = pair.first - dir, uri, branch = pair.last.values_at *%w[dir uri branch] - branch = esc branch + dir, uri, branch, tag = pair.last.values_at *%w[dir uri branch tag] + checkout = esc(tag ? tag : branch) + merge = esc(tag ? tag : "origin/#{branch}") subm = "git submodule update --init --recursive 2>&1" exists = File.directory? dir ok, result = if exists - dir = esc dir + dir = iswin ? dir : esc(dir) ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil, nil current_uri = data.lines.to_a.last if !ret @@ -1100,7 +1583,8 @@ function! s:update_ruby() else if pull log.call name, 'Updating ...', :update - bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && (git pull --no-rebase origin #{branch} #{progress} 2>&1 && #{subm})", name, :update, nil + fetch_opt = (tag && File.exist?(File.join(dir, '.git/shallow'))) ? '--depth 99999999' : '' + bt.call "#{cd} #{dir} && git fetch #{fetch_opt} #{progress} 2>&1 && git checkout -q #{checkout} 2>&1 && git merge --ff-only #{merge} 2>&1 && #{subm}", name, :update, nil else [true, skip] end @@ -1108,7 +1592,7 @@ function! s:update_ruby() else d = esc dir.sub(%r{[\\/]+$}, '') log.call name, 'Installing ...', :install - bt.call "git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1", name, :install, proc { + bt.call "git clone #{clone_opt unless tag} #{progress} --recursive #{uri} -b #{checkout} #{d} 2>&1", name, :install, proc { FileUtils.rm_rf dir } end @@ -1126,7 +1610,7 @@ EOF endfunction function! s:shellesc(arg) - return '"'.substitute(a:arg, '"', '\\"', 'g').'"' + return '"'.escape(a:arg, '"').'"' endfunction function! s:glob_dir(path) @@ -1153,12 +1637,20 @@ function! s:format_message(bullet, name, message) endfunction function! s:with_cd(cmd, dir) - return 'cd '.s:esc(a:dir).' && '.a:cmd + return printf('cd%s %s && %s', s:is_win ? ' /d' : '', s:shellesc(a:dir), a:cmd) endfunction function! s:system(cmd, ...) - let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd - return system(s:is_win ? '('.cmd.')' : cmd) + try + let [sh, shrd] = [&shell, &shellredir] + if !s:is_win + set shell=sh shellredir=>%s\ 2>&1 + endif + let cmd = a:0 > 0 ? s:with_cd(a:cmd, a:1) : a:cmd + return system(s:is_win ? '('.cmd.')' : cmd) + finally + let [&shell, &shellredir] = [sh, shrd] + endtry endfunction function! s:system_chomp(...) @@ -1182,13 +1674,19 @@ function! s:git_valid(spec, check_branch) let ret = 0 elseif a:check_branch let branch = result[0] - if a:spec.branch !=# branch + " Check tag + if has_key(a:spec, 'tag') let tag = s:system_chomp('git describe --exact-match --tags HEAD 2>&1', a:spec.dir) - if a:spec.branch !=# tag - let msg = printf('Invalid branch/tag: %s (expected: %s). Try PlugUpdate.', - \ (empty(tag) ? branch : tag), a:spec.branch) + if a:spec.tag !=# tag + let msg = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + \ (empty(tag) ? 'N/A' : tag), a:spec.tag) let ret = 0 endif + " Check branch + elseif a:spec.branch !=# branch + let msg = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', + \ branch, a:spec.branch) + let ret = 0 endif endif else @@ -1198,6 +1696,12 @@ function! s:git_valid(spec, check_branch) return [ret, msg] endfunction +function! s:rm_rf(dir) + if isdirectory(a:dir) + call s:system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(a:dir)) + endif +endfunction + function! s:clean(force) call s:prepare() call append(0, 'Searching for unused plugins in '.g:plug_home) @@ -1218,6 +1722,7 @@ function! s:clean(force) let allowed = {} for dir in dirs + let allowed[s:dirpath(fnamemodify(dir, ':h:h'))] = 1 let allowed[dir] = 1 for child in s:glob_dir(dir) let allowed[child] = 1 @@ -1241,13 +1746,11 @@ function! s:clean(force) call append(line('$'), 'Already clean.') else call inputsave() - let yes = a:force || (input('Proceed? (Y/N) ') =~? '^y') + let yes = a:force || (input('Proceed? (y/N) ') =~? '^y') call inputrestore() if yes for dir in todo - if isdirectory(dir) - call system((s:is_win ? 'rmdir /S /Q ' : 'rm -rf ') . s:shellesc(dir)) - endif + call s:rm_rf(dir) endfor call append(line('$'), 'Removed.') else @@ -1258,40 +1761,30 @@ function! s:clean(force) endfunction function! s:upgrade() - let new = s:me . '.new' - echo 'Downloading '. s:plug_src + echo 'Downloading the latest version of vim-plug' redraw + let tmp = tempname() + let new = tmp . '/plug.vim' + try - if executable('curl') - let output = system(printf('curl -fLo %s %s', s:shellesc(new), s:plug_src)) - if v:shell_error - throw get(s:lines(output), -1, v:shell_error) - endif - elseif has('ruby') - ruby << EOF - require 'open-uri' - File.open(VIM::evaluate('new'), 'w') do |f| - f << open(VIM::evaluate('s:plug_src')).read - end -EOF + let out = s:system(printf('git clone --depth 1 %s %s', s:plug_src, tmp)) + if v:shell_error + return s:err('Error upgrading vim-plug: '. out) + endif + + if readfile(s:me) ==# readfile(new) + echo 'vim-plug is already up-to-date' + return 0 else - return s:err('curl executable or ruby support not found') + call rename(s:me, s:me . '.old') + call rename(new, s:me) + unlet g:loaded_plug + echo 'vim-plug has been upgraded' + return 1 endif - catch - return s:err('Error upgrading vim-plug: '. v:exception) + finally + silent! call s:rm_rf(tmp) endtry - - if readfile(s:me) ==# readfile(new) - echo 'vim-plug is up-to-date' - silent! call delete(new) - return 0 - else - call rename(s:me, s:me . '.old') - call rename(new, s:me) - unlet g:loaded_plug - echo 'vim-plug is upgraded' - return 1 - endif endfunction function! s:upgrade_specs() @@ -1408,9 +1901,11 @@ function! s:preview_commit() execute 'pedit' sha wincmd P - setlocal filetype=git buftype=nofile nobuflisted - execute 'silent read !cd' s:esc(g:plugs[name].dir) '&& git show' sha + setlocal filetype=git buftype=nofile nobuflisted modifiable + execute 'silent read !cd' s:shellesc(g:plugs[name].dir) '&& git show --pretty=medium' sha normal! gg"_dd + setlocal nomodifiable + nnoremap q :q wincmd p endfunction @@ -1443,6 +1938,7 @@ function! s:diff() call setline(1, cnt == 0 ? 'No updates.' : 'Last update:') nnoremap :silent! call preview_commit() + nnoremap o :silent! call preview_commit() nnoremap X :call revert() normal! gg setlocal nomodifiable @@ -1454,7 +1950,7 @@ endfunction function! s:revert() let name = s:find_name(line('.')) if empty(name) || !has_key(g:plugs, name) || - \ input(printf('Revert the update of %s? (Y/N) ', name)) !~? '^y' + \ input(printf('Revert the update of %s? (y/N) ', name)) !~? '^y' return endif @@ -1470,9 +1966,9 @@ function! s:snapshot(...) abort let [type, var, header] = s:is_win ? \ ['dosbatch', '%PLUG_HOME%', \ ['@echo off', ':: Generated by vim-plug', ':: '.strftime("%c"), '', - \ ':: Make sure to PlugUpdate first', '', 'set PLUG_HOME='.s:esc(home)]] : + \ ':: Make sure to PlugUpdate first', '', 'set PLUG_HOME='.home]] : \ ['sh', '$PLUG_HOME', - \ ['#!/bin/bash', '# Generated by vim-plug', '# '.strftime("%c"), '', + \ ['#!/bin/sh', '# Generated by vim-plug', '# '.strftime("%c"), '', \ 'vim +PlugUpdate +qa', '', 'PLUG_HOME='.s:esc(home)]] call s:prepare() @@ -1489,17 +1985,18 @@ function! s:snapshot(...) abort let sha = s:system_chomp('git rev-parse --short HEAD', dir) if !empty(sha) call append(anchor, printf('cd %s && git reset --hard %s', - \ substitute(dir, '^'.g:plug_home, var, ''), sha)) + \ substitute(dir, '^\V'.escape(g:plug_home, '\'), var, ''), sha)) redraw endif endfor if a:0 > 0 - let fn = s:esc(expand(a:1)) + let fn = expand(a:1) + let fne = s:esc(fn) call writefile(getline(1, '$'), fn) - if !s:is_win | call system('chmod +x ' . fn) | endif + if !s:is_win | call s:system('chmod +x ' . fne) | endif echo 'Saved to '.a:1 - silent execute 'e' fn + silent execute 'e' fne endif endfunction diff --git a/vimrc b/vimrc index 7b1bca6..d39bbae 100644 --- a/vimrc +++ b/vimrc @@ -13,6 +13,7 @@ Plug 'tpope/vim-surround' Plug 'tpope/vim-fugitive' Plug 'tpope/vim-endwise' Plug 'tpope/vim-vinegar' +Plug 'tpope/vim-unimpaired' Plug 'tpope/vim-characterize' Plug 'tpope/vim-speeddating' Plug 'tpope/vim-jdaddy'