Jelajahi Sumber

Real-time progress report

This commit is almost identical to the reverted
60b907c87f693e3e4ce2cc91aa10d9125cfe441e, but it tries to avoid high CPU load
by not printing every line.
Junegunn Choi 11 tahun lalu
induk
melakukan
af89938633
2 mengubah file dengan 84 tambahan dan 24 penghapusan
  1. 83 23
      plug.vim
  2. 1 1
      test/workflow.vader

+ 83 - 23
plug.vim

@@ -323,7 +323,11 @@ function! s:syntax()
   syn match plugBracket /[[\]]/ contained
   syn match plugBracket /[[\]]/ contained
   syn match plugX /x/ contained
   syn match plugX /x/ contained
   syn match plugDash /^-/
   syn match plugDash /^-/
+  syn match plugPlus /^+/
+  syn match plugStar /^*/
   syn match plugName /\(^- \)\@<=[^:]*/
   syn match plugName /\(^- \)\@<=[^:]*/
+  syn match plugInstall /\(^+ \)\@<=[^:]*/
+  syn match plugUpdate /\(^* \)\@<=[^:]*/
   syn match plugCommit /^  [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
   syn match plugCommit /^  [0-9a-z]\{7} .*/ contains=plugRelDate,plugSha
   syn match plugSha /\(^  \)\@<=[0-9a-z]\{7}/ contained
   syn match plugSha /\(^  \)\@<=[0-9a-z]\{7}/ contained
   syn match plugRelDate /([^)]*)$/ contained
   syn match plugRelDate /([^)]*)$/ contained
@@ -334,8 +338,15 @@ function! s:syntax()
   hi def link plugX       Exception
   hi def link plugX       Exception
   hi def link plugBracket Structure
   hi def link plugBracket Structure
   hi def link plugNumber  Number
   hi def link plugNumber  Number
+
   hi def link plugDash    Special
   hi def link plugDash    Special
+  hi def link plugPlus    Constant
+  hi def link plugStar    Boolean
+
   hi def link plugName    Label
   hi def link plugName    Label
+  hi def link plugInstall Function
+  hi def link plugUpdate  Type
+
   hi def link plugError   Error
   hi def link plugError   Error
   hi def link plugRelDate Comment
   hi def link plugRelDate Comment
   hi def link plugSha     Identifier
   hi def link plugSha     Identifier
@@ -429,7 +440,21 @@ function! s:update_impl(pull, args) abort
 
 
   let len = len(g:plugs)
   let len = len(g:plugs)
   if has('ruby') && threads > 1
   if has('ruby') && threads > 1
-    call s:update_parallel(a:pull, todo, threads)
+    try
+      call s:update_parallel(a:pull, todo, threads)
+    catch
+      let lines = getline(4, '$')
+      let printed = {}
+      silent 4,$d
+      for line in lines
+        let name = get(matchlist(line, '^. \([^:]\+\):'), 1, '')
+        if empty(name) || !has_key(printed, name)
+          let printed[name] = 1
+          call append('$', line)
+        endif
+      endfor
+      echoerr v:exception
+    endtry
   else
   else
     call s:update_serial(a:pull, todo)
     call s:update_serial(a:pull, todo)
   endif
   endif
@@ -491,7 +516,6 @@ function! s:update_serial(pull, todo)
         if !isdirectory(base)
         if !isdirectory(base)
           call mkdir(base, 'p')
           call mkdir(base, 'p')
         endif
         endif
-        execute 'cd '.base
         let result = s:system(
         let result = s:system(
               \ printf('git clone --recursive %s -b %s %s 2>&1 && cd %s && git submodule update --init --recursive 2>&1',
               \ printf('git clone --recursive %s -b %s %s 2>&1 && cd %s && git submodule update --init --recursive 2>&1',
               \ s:shellesc(spec.uri),
               \ s:shellesc(spec.uri),
@@ -520,6 +544,23 @@ endfunction
 
 
 function! s:update_parallel(pull, todo, threads)
 function! s:update_parallel(pull, todo, threads)
   ruby << EOF
   ruby << EOF
+  module PlugStream
+    SEP = ["\r", "\n", nil]
+    def get_line
+      buffer = ''
+      loop do
+        char = readchar rescue return
+        if SEP.include? char
+          buffer << $/
+          break
+        else
+          buffer << char
+        end
+      end
+      buffer
+    end
+  end unless defined?(PlugStream)
+
   def esc arg
   def esc arg
     %["#{arg.gsub('"', '\"')}"]
     %["#{arg.gsub('"', '\"')}"]
   end
   end
@@ -535,55 +576,70 @@ function! s:update_parallel(pull, todo, threads)
   all   = VIM::evaluate('copy(a:todo)')
   all   = VIM::evaluate('copy(a:todo)')
   limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
   limit = VIM::evaluate('get(g:, "plug_timeout", 60)')
   nthr  = VIM::evaluate('a:threads').to_i
   nthr  = VIM::evaluate('a:threads').to_i
+  maxy  = VIM::evaluate('winheight(".")').to_i
   cd    = iswin ? 'cd /d' : 'cd'
   cd    = iswin ? 'cd /d' : 'cd'
-  done  = {}
   tot   = 0
   tot   = 0
   bar   = ''
   bar   = ''
   skip  = 'Already installed'
   skip  = 'Already installed'
   mtx   = Mutex.new
   mtx   = Mutex.new
   take1 = proc { mtx.synchronize { running && all.shift } }
   take1 = proc { mtx.synchronize { running && all.shift } }
   logh  = proc {
   logh  = proc {
-    cnt = done.length
+    cnt = $curbuf[2][1...-1].strip.length
     tot = VIM::evaluate('len(a:todo)') || tot
     tot = VIM::evaluate('len(a:todo)') || tot
     $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
     $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})"
     $curbuf[2] = '[' + bar.ljust(tot) + ']'
     $curbuf[2] = '[' + bar.ljust(tot) + ']'
     VIM::command('normal! 2G')
     VIM::command('normal! 2G')
     VIM::command('redraw') unless iswin
     VIM::command('redraw') unless iswin
   }
   }
-  log = proc { |name, result, ok|
+  where = proc { |name| (1..($curbuf.length)).find { |l| $curbuf[l] =~ /^[-+x*] #{name}:/ } }
+  log   = proc { |name, result, type|
     mtx.synchronize do
     mtx.synchronize do
-      bar += ok ? '=' : 'x'
-      done[name] = true
+      ing  = ![true, false].include?(type)
+      bar += type ? '=' : 'x' unless ing
+      b = case type
+          when :install  then '+' when :update then '*'
+          when true, nil then '-' else 'x' end
       result =
       result =
-        if ok
-          ["- #{name}: #{result.lines.to_a.last.strip}"]
+        if type || type.nil?
+          ["#{b} #{name}: #{result.lines.to_a.last}"]
         elsif result =~ /^Interrupted|^Timeout/
         elsif result =~ /^Interrupted|^Timeout/
-          ["x #{name}: #{result}"]
+          ["#{b} #{name}: #{result}"]
         else
         else
-          ["x #{name}"] + result.lines.map { |l| "    " << l }
+          ["#{b} #{name}"] + result.lines.map { |l| "    " << l }
         end
         end
+      if lnum = where.call(name)
+        $curbuf.delete lnum
+        lnum = 4 if ing && lnum > maxy
+      end
       result.each_with_index do |line, offset|
       result.each_with_index do |line, offset|
-        $curbuf.append 3 + offset, line.chomp
+        $curbuf.append (lnum || 4) - 1 + offset, line.gsub(/\e\[./, '').chomp
       end
       end
       logh.call
       logh.call
     end
     end
   }
   }
-  bt = proc { |cmd|
+  bt = proc { |cmd, name, type|
     begin
     begin
       fd = nil
       fd = nil
-      Timeout::timeout(limit) do
-        if iswin
+      data = ''
+      if iswin
+        Timeout::timeout(limit) do
           tmp = VIM::evaluate('tempname()')
           tmp = VIM::evaluate('tempname()')
           system("#{cmd} > #{tmp}")
           system("#{cmd} > #{tmp}")
           data = File.read(tmp).chomp
           data = File.read(tmp).chomp
           File.unlink tmp rescue nil
           File.unlink tmp rescue nil
-        else
-          fd = IO.popen(cmd)
-          data = fd.read.chomp
-          fd.close
         end
         end
-        [$? == 0, data]
+      else
+        fd = IO.popen(cmd).extend(PlugStream)
+        first_line = true
+        log_prob = 1.0 / nthr
+        while line = Timeout::timeout(limit) { fd.get_line }
+          data << line
+          log.call name, line.chomp, type if name && (first_line || rand < log_prob)
+          first_line = false
+        end
+        fd.close
       end
       end
+      [$? == 0, data.chomp]
     rescue Timeout::Error, Interrupt => e
     rescue Timeout::Error, Interrupt => e
       if fd && !fd.closed?
       if fd && !fd.closed?
         pids = [fd.pid]
         pids = [fd.pid]
@@ -616,6 +672,7 @@ function! s:update_parallel(pull, todo, threads)
     main.kill
     main.kill
   }
   }
 
 
+  progress = iswin ? '' : '--progress'
   until all.empty?
   until all.empty?
     names = all.keys
     names = all.keys
     [names.length, nthr].min.times do
     [names.length, nthr].min.times do
@@ -625,10 +682,11 @@ function! s:update_parallel(pull, todo, threads)
             name = pair.first
             name = pair.first
             dir, uri, branch = pair.last.values_at *%w[dir uri branch]
             dir, uri, branch = pair.last.values_at *%w[dir uri branch]
             branch = esc branch
             branch = esc branch
+            subm = "git submodule update --init --recursive 2>&1"
             ok, result =
             ok, result =
               if File.directory? dir
               if File.directory? dir
                 dir = esc dir
                 dir = esc dir
-                ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url"
+                ret, data = bt.call "#{cd} #{dir} && git rev-parse --abbrev-ref HEAD 2>&1 && git config remote.origin.url", nil, nil
                 current_uri = data.lines.to_a.last
                 current_uri = data.lines.to_a.last
                 if !ret
                 if !ret
                   if data =~ /^Interrupted|^Timeout/
                   if data =~ /^Interrupted|^Timeout/
@@ -642,7 +700,8 @@ function! s:update_parallel(pull, todo, threads)
                            "PlugClean required."].join($/)]
                            "PlugClean required."].join($/)]
                 else
                 else
                   if pull
                   if pull
-                    bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && git pull origin #{branch} 2>&1 && git submodule update --init --recursive 2>&1"
+                    log.call name, 'Updating ...', :update
+                    bt.call "#{cd} #{dir} && git checkout -q #{branch} 2>&1 && (git pull origin #{branch} #{progress} 2>&1 && #{subm})", name, :update
                   else
                   else
                     [true, skip]
                     [true, skip]
                   end
                   end
@@ -650,7 +709,8 @@ function! s:update_parallel(pull, todo, threads)
               else
               else
                 FileUtils.mkdir_p(base)
                 FileUtils.mkdir_p(base)
                 d = esc dir.sub(%r{[\\/]+$}, '')
                 d = esc dir.sub(%r{[\\/]+$}, '')
-                bt.call "#{cd} #{base} && git clone --recursive #{uri} -b #{branch} #{d} 2>&1 && cd #{esc dir} && git submodule update --init --recursive 2>&1"
+                log.call name, 'Installing ...', :install
+                bt.call "(git clone #{progress} --recursive #{uri} -b #{branch} #{d} 2>&1 && cd #{esc dir} && #{subm})", name, :install
               end
               end
             log.call name, result, ok
             log.call name, result, ok
           end
           end

+ 1 - 1
test/workflow.vader

@@ -239,7 +239,7 @@ Execute (PlugClean! to remove vim-emoji):
 
 
 Execute (PlugUpdate to install both again):
 Execute (PlugUpdate to install both again):
   PlugUpdate
   PlugUpdate
-  AssertEqual 2, len(filter(getline(1, line('$')), 'v:val =~ "Cloning into"'))
+  AssertEqual 2, len(filter(getline(1, line('$')), 'v:val =~ "^- [^:]*:"'))
   AssertEqual 3, g:vimrc_reloaded
   AssertEqual 3, g:vimrc_reloaded
   Assert !empty(globpath(&rtp, 'colors/seoul256.vim')), 'seoul256.vim should be found'
   Assert !empty(globpath(&rtp, 'colors/seoul256.vim')), 'seoul256.vim should be found'
   Assert !empty(globpath(&rtp, 'autoload/emoji.vim')), 'vim-emoji should be found'
   Assert !empty(globpath(&rtp, 'autoload/emoji.vim')), 'vim-emoji should be found'