diff --git a/.vim/autoload/plug.vim b/.vim/autoload/plug.vim index 60e6bf5..2969692 100644 --- a/.vim/autoload/plug.vim +++ b/.vim/autoload/plug.vim @@ -611,7 +611,7 @@ function! s:prepare() silent %d _ else call s:new_window() - nnoremap q :if b:plug_preview==1pcendifechoq + nnoremap q :if b:plug_preview==1pcendifbd nnoremap R :silent! call retry() nnoremap D :PlugDiff nnoremap S :PlugStatus @@ -687,6 +687,38 @@ function! s:do(pull, force, todo) endfor endfunction +function! s:hash_match(a, b) + return stridx(a:a, a:b) == 0 || stridx(a:b, a:a) == 0 +endfunction + +function! s:checkout(plugs) + for [name, spec] in items(a:plugs) + let sha = spec.commit + call append(3, '- Checking out '.sha[:6].' of '.name.' ... ') + redraw + + let error = [] + let output = s:lines(s:system('git rev-parse HEAD', spec.dir)) + if v:shell_error + let error = output + elseif !s:hash_match(sha, output[0]) + let output = s:lines(s:system( + \ 'git fetch --depth 999999 && git checkout '.sha, spec.dir)) + if v:shell_error + let error = output + endif + endif + if empty(error) + call setline(4, getline(4) . 'OK') + else + call setline(4, 'x'.getline(4)[1:] . 'Error') + for line in reverse(error) + call append(4, ' '.line) + endfor + endif + endfor +endfunction + function! s:finish(pull) let new_frozen = len(filter(keys(s:update.new), 'g:plugs[v:val].frozen')) if new_frozen @@ -729,7 +761,7 @@ 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', s:is_win ? 1 : 16) + \ remove(args, -1) : get(g:, 'plug_threads', 16) let managed = filter(copy(g:plugs), 's:is_managed(v:key)') let todo = empty(args) ? filter(managed, '!v:val.frozen || !isdirectory(v:val.dir)') : @@ -766,9 +798,8 @@ function! s:update_impl(pull, force, args) abort 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 python = (has('python') || has('python3')) && (!s:nvim || has('vim_starting')) + let ruby = has('ruby') && !s:nvim && (v:version >= 703 || v:version == 702 && has('patch374')) && !(s:is_win && has('gui_running')) let s:update = { \ 'start': reltime(), @@ -841,6 +872,7 @@ function! s:update_finish() let $GIT_TERMINAL_PROMPT = s:git_terminal_prompt endif if s:switch_in() + call s:checkout(filter(copy(s:update.all), 'has_key(v:val, "commit")')) 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) call setline(1, 'Updated. Elapsed time: ' . split(reltimestr(reltime(s:update.start)))[0] . ' sec.') @@ -870,7 +902,13 @@ function! s:job_handler(job_id, data, event) abort endif if a:event == 'stdout' - let self.result .= substitute(s:to_s(a:data), '[\r\n]', '', 'g') . "\n" + let complete = empty(a:data[-1]) + let lines = map(filter(a:data, 'len(v:val) > 0'), 'split(v:val, "[\r\n]")[-1]') + call extend(self.lines, lines) + let self.result = join(self.lines, "\n") + if !complete + call remove(self.lines, -1) + endif " To reduce the number of buffer updates let self.tick = get(self, 'tick', -1) + 1 if self.tick % len(s:jobs) == 0 @@ -887,7 +925,7 @@ function! s:job_handler(job_id, data, event) abort endfunction function! s:spawn(name, cmd, opts) - let job = { 'name': a:name, 'running': 1, 'error': 0, 'result': '', + let job = { 'name': a:name, 'running': 1, 'error': 0, 'lines': [], 'result': '', \ 'new': get(a:opts, 'new', 0), \ 'on_stdout': function('s:job_handler'), \ 'on_exit' : function('s:job_handler'), @@ -995,8 +1033,8 @@ while 1 " Without TCO, Vim stack is bound to explode let merge = s:shellesc(has_tag ? spec.tag : 'origin/'.spec.branch) if !new - let [valid, msg] = s:git_valid(spec, 0) - if valid + let error = s:git_validate(spec, 0) + if empty(error) if pull let fetch_opt = (has_tag && !empty(globpath(spec.dir, '.git/shallow'))) ? '--depth 99999999' : '' call s:spawn(name, @@ -1006,7 +1044,7 @@ while 1 " Without TCO, Vim stack is bound to explode let s:jobs[name] = { 'running': 0, 'result': 'Already installed', 'error': 0 } endif else - let s:jobs[name] = { 'running': 0, 'result': msg, 'error': 1 } + let s:jobs[name] = { 'running': 0, 'result': error, 'error': 1 } endif else call s:spawn(name, @@ -1028,9 +1066,8 @@ endwhile endfunction function! s:update_python() -let py_exe = has('python3') ? 'python3' : 'python' +let py_exe = has('python') ? 'python' : 'python3' execute py_exe "<< EOF" -""" Due to use of signals this function is POSIX only. """ import datetime import functools import os @@ -1057,14 +1094,11 @@ 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 = {} +G_IS_WIN = vim.eval('s:is_win') == '1' class PlugError(Exception): def __init__(self, msg): - self._msg = msg - @property - def msg(self): - return self._msg + self.msg = msg class CmdTimedOut(PlugError): pass class CmdFailed(PlugError): @@ -1075,10 +1109,9 @@ class Action(object): INSTALL, UPDATE, ERROR, DONE = ['+', '*', 'x', '-'] class Buffer(object): - def __init__(self, lock, num_plugs, is_pull, is_win): + def __init__(self, lock, num_plugs, is_pull): 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 @@ -1106,8 +1139,7 @@ class Buffer(object): with self.lock: vim.command('normal! 2G') - if not self.is_win: - vim.command('redraw') + vim.command('redraw') def write(self, action, name, lines): first, rest = lines[0], lines[1:] @@ -1138,7 +1170,8 @@ class Buffer(object): class Command(object): def __init__(self, cmd, cmd_dir=None, timeout=60, cb=None, clean=None): self.cmd = cmd - self.cmd_dir = cmd_dir + if cmd_dir: + self.cmd = 'cd {0} && {1}'.format(cmd_dir, self.cmd) self.timeout = timeout self.callback = cb if cb else (lambda msg: None) self.clean = clean if clean else (lambda: None) @@ -1188,9 +1221,11 @@ class Command(object): try: tfile = tempfile.NamedTemporaryFile(mode='w+b') - self.proc = subprocess.Popen(self.cmd, cwd=self.cmd_dir, stdout=tfile, - stderr=subprocess.STDOUT, shell=True, - preexec_fn=os.setsid) + preexec_fn = not G_IS_WIN and os.setsid or None + self.proc = subprocess.Popen(self.cmd, stdout=tfile, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, shell=True, + preexec_fn=preexec_fn) thrd = thr.Thread(target=(lambda proc: proc.wait()), args=(self.proc,)) thrd.start() @@ -1208,7 +1243,7 @@ class Command(object): if first_line or random.random() < G_LOG_PROB: first_line = False - line = nonblock_read(tfile.name) + line = '' if G_IS_WIN else nonblock_read(tfile.name) if line: self.callback([line]) @@ -1232,7 +1267,10 @@ class Command(object): def terminate(self): """ Terminate process and cleanup. """ if self.alive: - os.killpg(self.proc.pid, signal.SIGTERM) + if G_IS_WIN: + os.kill(self.proc.pid, signal.SIGINT) + else: + os.killpg(self.proc.pid, signal.SIGTERM) self.clean() class Plugin(object): @@ -1338,10 +1376,6 @@ class PlugThread(thr.Thread): 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): @@ -1387,25 +1421,23 @@ def 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 = Buffer(lock, len(plugs), G_PULL) buf_q, work_q = queue.Queue(), queue.Queue() for work in plugs.items(): work_q.put(work) - global G_THREADS + start_cnt = thr.active_count() 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: + while not buf_q.empty() or thr.active_count() != start_cnt: try: action, name, msg = buf_q.get(True, 0.25) buf.write(action, name, msg) @@ -1448,16 +1480,20 @@ function! s:update_ruby() def killall pid pids = [pid] - unless `which pgrep 2> /dev/null`.empty? - children = pids - until children.empty? - children = children.map { |pid| - `pgrep -P #{pid}`.lines.map { |l| l.chomp } - }.flatten - pids += children + if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + pids.each { |pid| Process.kill 'INT', pid.to_i rescue nil } + else + unless `which pgrep 2> /dev/null`.empty? + children = pids + until children.empty? + children = children.map { |pid| + `pgrep -P #{pid}`.lines.map { |l| l.chomp } + }.flatten + pids += children + end end + pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } end - pids.each { |pid| Process.kill 'TERM', pid.to_i rescue nil } end require 'thread' @@ -1483,7 +1519,7 @@ function! s:update_ruby() $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" $curbuf[2] = '[' + bar.ljust(tot) + ']' VIM::command('normal! 2G') - VIM::command('redraw') unless iswin + VIM::command('redraw') } where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } } log = proc { |name, result, type| @@ -1595,8 +1631,8 @@ function! s:update_ruby() exists = File.directory? dir ok, result = if exists - 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 + chdir = "#{cd} #{iswin ? dir : esc(dir)}" + ret, data = bt.call "#{chdir} && 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 if data =~ /^Interrupted|^Timeout/ @@ -1612,7 +1648,7 @@ function! s:update_ruby() if pull log.call name, 'Updating ...', :update 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 + bt.call "#{chdir} && 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 @@ -1686,42 +1722,46 @@ function! s:system_chomp(...) return v:shell_error ? '' : substitute(ret, '\n$', '', '') endfunction -function! s:git_valid(spec, check_branch) - let ret = 1 - let msg = 'OK' +function! s:git_validate(spec, check_branch) + let err = '' if isdirectory(a:spec.dir) let result = s:lines(s:system('git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url', a:spec.dir)) let remote = result[-1] if v:shell_error - let msg = join([remote, 'PlugClean required.'], "\n") - let ret = 0 + let err = join([remote, 'PlugClean required.'], "\n") elseif !s:compare_git_uri(remote, a:spec.uri) - let msg = join(['Invalid URI: '.remote, + let err = join(['Invalid URI: '.remote, \ 'Expected: '.a:spec.uri, \ 'PlugClean required.'], "\n") - let ret = 0 + elseif a:check_branch && has_key(a:spec, 'commit') + let result = s:lines(s:system('git rev-parse HEAD 2>&1', a:spec.dir)) + let sha = result[-1] + if v:shell_error + let err = join(add(result, 'PlugClean required.'), "\n") + elseif !s:hash_match(sha, a:spec.commit) + let err = join([printf('Invalid HEAD (expected: %s, actual: %s)', + \ a:spec.commit[:6], sha[:6]), + \ 'PlugUpdate required.'], "\n") + endif elseif a:check_branch let branch = result[0] " 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.tag !=# tag - let msg = printf('Invalid tag: %s (expected: %s). Try PlugUpdate.', + let err = 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.', + let err = printf('Invalid branch: %s (expected: %s). Try PlugUpdate.', \ branch, a:spec.branch) - let ret = 0 endif endif else - let msg = 'Not found' - let ret = 0 + let err = 'Not found' endif - return [ret, msg] + return err endfunction function! s:rm_rf(dir) @@ -1739,7 +1779,7 @@ function! s:clean(force) let dirs = [] let [cnt, total] = [0, len(g:plugs)] for [name, spec] in items(g:plugs) - if !s:is_managed(name) || s:git_valid(spec, 0)[0] + if !s:is_managed(name) || empty(s:git_validate(spec, 0)) call add(dirs, spec.dir) endif let cnt += 1 @@ -1832,7 +1872,8 @@ function! s:status() for [name, spec] in items(g:plugs) if has_key(spec, 'uri') if isdirectory(spec.dir) - let [valid, msg] = s:git_valid(spec, 1) + let err = s:git_validate(spec, 1) + let [valid, msg] = [empty(err), empty(err) ? 'OK' : err] else let [valid, msg] = [0, 'Not found. Try PlugInstall.'] endif @@ -1948,7 +1989,7 @@ function! s:diff() redraw let cnt = 0 - for [k, v] in items(g:plugs) + for [k, v] in filter(items(g:plugs), '!has_key(v:val[1], "commit")') if !isdirectory(v.dir) || !s:is_managed(k) continue endif @@ -2007,7 +2048,7 @@ function! s:snapshot(...) abort redraw let dirs = sort(map(values(filter(copy(g:plugs), - \'has_key(v:val, "uri") && isdirectory(v:val.dir)')), 'v:val.dir')) + \'has_key(v:val, "uri") && !has_key(v:val, "commit") && isdirectory(v:val.dir)')), 'v:val.dir')) let anchor = line('$') - 1 for dir in reverse(dirs) let sha = s:system_chomp('git rev-parse --short HEAD', dir)