Explorar o código

Neovim Python Support

* Buffer updates now managed by __main__ loop with buf_q.
* Synchronous neovim install temporarily provided by python installer.
* Known issues:
*   No ctrl-c/interrupt support on nvim.
*   Graphical bug: https://github.com/neovim/python-client/issues/103
Jeremy Pallats/starcraft.man %!s(int64=10) %!d(string=hai) anos
pai
achega
9bee42ca0a
Modificáronse 2 ficheiros con 61 adicións e 44 borrados
  1. 1 3
      .travis.yml
  2. 60 41
      plug.vim

+ 1 - 3
.travis.yml

@@ -7,19 +7,17 @@ rvm:
   - 2.1.0 # Test against python3 installer
 
 before_script: |
+  sudo apt-get update -y
   if [ $(ruby -e 'puts RUBY_VERSION') = 1.9.2 ]; then
-    sudo apt-get update -y
     sudo apt-get install -y vim-nox
     sudo ln -s /usr/bin/vim /usr/local/bin/vim
   else
     git clone --depth 1 https://github.com/vim/vim
     cd vim
     if [ $(ruby -e 'puts RUBY_VERSION') = 1.9.3 ]; then
-      sudo apt-get update -y
       sudo apt-get install -y python2.7-dev
       ./configure --with-features=huge --enable-pythoninterp
     elif [ $(ruby -e 'puts RUBY_VERSION') = 2.1.0 ]; then
-      sudo apt-get update -y
       sudo apt-get install -y python3-dev
       ./configure --with-features=huge --enable-python3interp
     else

+ 60 - 41
plug.vim

@@ -72,9 +72,6 @@ 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:py = (has('python') || has('python3')) && !has('nvim') && !s:is_win && !has('win32unix')
-let s:py_exe = has('python3') ? 'python3' : 'python'
-let s:ruby = has('ruby') && !has('nvim') && (v:version >= 703 || v:version == 702 && has('patch374'))
 let s:nvim = has('nvim') && exists('*jobwait') && !s:is_win
 let s:me = resolve(expand('<sfile>:p'))
 let s:base_spec = { 'branch': 'master', 'frozen': 0 }
@@ -747,6 +744,10 @@ 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 s:update = {
     \ 'start':   reltime(),
     \ 'all':     todo,
@@ -755,7 +756,7 @@ function! s:update_impl(pull, force, args) abort
     \ 'pull':    a:pull,
     \ 'force':   a:force,
     \ 'new':     {},
-    \ 'threads': (s:py || s:ruby || s:nvim) ? min([len(todo), threads]) : 1,
+    \ 'threads': (python || ruby || s:nvim) ? min([len(todo), threads]) : 1,
     \ 'bar':     '',
     \ 'fin':     0
   \ }
@@ -768,20 +769,21 @@ function! s:update_impl(pull, force, args) abort
         \ '--depth 1' . (s:git_version_requirement(1, 7, 10) ? ' --no-single-branch' : '') : ''
 
   " Python version requirement (>= 2.7)
-  if s:py && !has('python3') && !s:ruby && !s:nvim && s:update.threads > 1
+  if python && !has('python3') && !ruby && !s:nvim && s:update.threads > 1
     redir => pyv
     silent python import platform; print(platform.python_version())
     redir END
-    let s:py = s:version_requirement(
+    let python = s:version_requirement(
           \ map(split(split(pyv)[0], '\.'), 'str2nr(v:val)'), [2, 6])
   endif
-  if (s:py || s:ruby) && !s:nvim && s:update.threads > 1
+
+  if (python || ruby) && s:update.threads > 1
     try
       let imd = &imd
       if s:mac_gui
         set noimd
       endif
-      if s:ruby
+      if ruby
         call s:update_ruby()
       else
         call s:update_python()
@@ -1003,7 +1005,8 @@ endwhile
 endfunction
 
 function! s:update_python()
-execute s:py_exe "<< EOF"
+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
@@ -1023,6 +1026,7 @@ 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)'))
@@ -1030,15 +1034,14 @@ 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
-  def _get_msg(self):
-    return self.msg
-  def _set_msg(self, msg):
     self._msg = msg
-  message = property(_get_msg, _set_msg)
+  @property
+  def msg(self):
+    return self._msg
 class CmdTimedOut(BaseExc):
   pass
 class CmdFailed(BaseExc):
@@ -1070,10 +1073,10 @@ class GLog(object):
       flog.write(msg.encode())
 
 class Buffer(object):
-  def __init__(self, lock, num_plugs):
+  def __init__(self, lock, num_plugs, is_pull, is_win):
     self.bar = ''
-    self.event = 'Updating' if vim.eval('s:update.pull') == '1' else 'Installing'
-    self.is_win = vim.eval('s:is_win') == '1'
+    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
@@ -1099,15 +1102,12 @@ class Buffer(object):
     num_spaces = self.num_plugs - len(self.bar)
     curbuf[1] = '[{0}{1}]'.format(self.bar, num_spaces * ' ')
 
-    vim.command('normal! 2G')
-    if not self.is_win:
-      vim.command('redraw')
-
-  def write(self, *args, **kwargs):
     with self.lock:
-      self._write(*args, **kwargs)
+      vim.command('normal! 2G')
+      if not self.is_win:
+        vim.command('redraw')
 
-  def _write(self, action, name, lines):
+  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]
@@ -1217,10 +1217,10 @@ class Command(object):
     return result
 
 class Plugin(object):
-  def __init__(self, name, args, buf, lock):
+  def __init__(self, name, args, buf_q, lock):
     self.name = name
     self.args = args
-    self.buf = buf
+    self.buf_q = buf_q
     self.lock = lock
     tag = args.get('tag', 0)
     self.checkout = esc(tag if tag else args['branch'])
@@ -1234,7 +1234,7 @@ class Plugin(object):
       else:
         self.install()
         with self.lock:
-          vim.command("let s:update.new['{0}'] = 1".format(self.name))
+          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:
@@ -1258,7 +1258,7 @@ class Plugin(object):
       return _clean
 
     self.write(Action.INSTALL, self.name, ['Installing ...'])
-    callback = functools.partial(self.buf.write, Action.INSTALL, self.name)
+    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))
@@ -1278,7 +1278,7 @@ class Plugin(object):
 
     if G_PULL:
       self.write(Action.UPDATE, self.name, ['Updating ...'])
-      callback = functools.partial(self.buf.write, Action.UPDATE, self.name)
+      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),
@@ -1301,7 +1301,7 @@ class Plugin(object):
 
   def write(self, action, name, msg):
     GLog.write('{0} {1}: {2}'.format(action, name, '\n'.join(msg)))
-    self.buf.write(action, name, msg)
+    self.buf_q.put((action, name, msg))
 
 class PlugThread(thr.Thread):
   def __init__(self, tname, args):
@@ -1311,17 +1311,21 @@ class PlugThread(thr.Thread):
 
   def run(self):
     thr.current_thread().name = self.tname
-    work_q, lock, buf = self.args
+    buf_q, work_q, lock = self.args
 
     try:
       while not G_STOP.is_set():
         name, args = work_q.get_nowait()
         GLog.write('{0}: Dir {1}'.format(name, args['dir']))
-        plug = Plugin(name, args, buf, lock)
+        plug = Plugin(name, args, buf_q, lock)
         plug.manage()
         work_q.task_done()
     except queue.Empty:
       GLog.write('Queue now empty.')
+    finally:
+      global G_THREADS
+      with lock:
+        del G_THREADS[thr.current_thread().name]
 
 class RefreshThread(thr.Thread):
   def __init__(self, lock):
@@ -1332,12 +1336,19 @@ class RefreshThread(thr.Thread):
   def run(self):
     while self.running:
       with self.lock:
-        vim.command('noautocmd normal! a')
+        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('"', '\"') + '"'
 
@@ -1361,7 +1372,6 @@ def main():
   if GLog.ON and os.path.exists(GLog.LOGDIR):
     shutil.rmtree(GLog.LOGDIR)
 
-  threads = []
   nthreads = int(vim.eval('s:update.threads'))
   plugs = vim.eval('s:update.todo')
   mac_gui = vim.eval('s:mac_gui') == '1'
@@ -1371,24 +1381,33 @@ def main():
   GLog.write('Num Threads: {0}'.format(nthreads))
 
   lock = thr.Lock()
-  buf = Buffer(lock, len(plugs))
-  work_q = queue.Queue()
+  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)
 
   GLog.write('Starting Threads')
+  global G_THREADS
   for num in range(nthreads):
     tname = 'PlugT-{0:02}'.format(num)
-    thread = PlugThread(tname, (work_q, lock, buf))
+    thread = PlugThread(tname, (buf_q, work_q, lock))
     thread.start()
-    threads.append(thread)
+    G_THREADS[tname] = thread
   if mac_gui:
     rthread = RefreshThread(lock)
     rthread.start()
 
-  GLog.write('Joining Live Threads')
-  for thread in threads:
-    thread.join()
+  GLog.write('Buffer Writing Loop')
+  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()