aboutsummaryrefslogtreecommitdiff
path: root/runtime/autoload
diff options
context:
space:
mode:
authorThiago de Arruda <tpadilha84@gmail.com>2014-11-18 15:57:42 -0300
committerThiago de Arruda <tpadilha84@gmail.com>2014-11-18 15:57:42 -0300
commit9e37c1d3b6b9d0d35d7f7558d3efb555757278c0 (patch)
treeabff58ba79dddfbf4d3667c7fdcf1642546f4c51 /runtime/autoload
parenta67fd6f21378bc01b41e5349abff2eb214f6c288 (diff)
parentd971cb169184a1fa59e93a5216635261d8c47e11 (diff)
downloadrneovim-9e37c1d3b6b9d0d35d7f7558d3efb555757278c0.tar.gz
rneovim-9e37c1d3b6b9d0d35d7f7558d3efb555757278c0.tar.bz2
rneovim-9e37c1d3b6b9d0d35d7f7558d3efb555757278c0.zip
Merge PR #1454 'Refactor plugin system'
Diffstat (limited to 'runtime/autoload')
-rw-r--r--runtime/autoload/provider/clipboard.vim40
-rw-r--r--runtime/autoload/provider/python.vim28
-rw-r--r--runtime/autoload/provider/script_host.py243
-rw-r--r--runtime/autoload/rpc/define.vim248
-rw-r--r--runtime/autoload/rpc/host.vim242
5 files changed, 801 insertions, 0 deletions
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
new file mode 100644
index 0000000000..615a80ca6d
--- /dev/null
+++ b/runtime/autoload/provider/clipboard.vim
@@ -0,0 +1,40 @@
+" The clipboard provider uses shell commands to communicate with the clipboard.
+" The provider function will only be registered if one of the supported
+" commands are available.
+let s:copy = ''
+let s:paste = ''
+
+if executable('pbcopy')
+ let s:copy = 'pbcopy'
+ let s:paste = 'pbpaste'
+elseif executable('xsel')
+ let s:copy = 'xsel -i -b'
+ let s:paste = 'xsel -o -b'
+elseif executable('xclip')
+ let s:copy = 'xclip -i -selection clipboard'
+ let s:paste = 'xclip -o -selection clipboard'
+endif
+
+if s:copy == ''
+ echom 'No shell command for communicating with the clipboard found.'
+ finish
+endif
+
+let s:methods = {}
+
+function! s:ClipboardGet(...)
+ return systemlist(s:paste)
+endfunction
+
+function! s:ClipboardSet(...)
+ call systemlist(s:copy, a:1)
+endfunction
+
+let s:methods = {
+ \ 'get': function('s:ClipboardGet'),
+ \ 'set': function('s:ClipboardSet')
+ \ }
+
+function! provider#clipboard#Call(method, args)
+ return s:methods[a:method](a:args)
+endfunction
diff --git a/runtime/autoload/provider/python.vim b/runtime/autoload/provider/python.vim
new file mode 100644
index 0000000000..9ca81c35f4
--- /dev/null
+++ b/runtime/autoload/provider/python.vim
@@ -0,0 +1,28 @@
+" The python provider uses a python host to emulate an environment for running
+" python-vim plugins(:h python-vim). See :h nvim-providers for more
+" information.
+"
+" Associating the plugin with the python host is the first step because plugins
+" will be passed as command-line arguments
+if exists('s:loaded_python_provider') || &cp
+ finish
+endif
+let s:loaded_python_provider = 1
+let s:plugin_path = expand('<sfile>:p:h').'/script_host.py'
+" The python provider plugin will run in a separate instance of the python
+" host.
+call rpc#host#RegisterClone('legacy-python-provider', 'python')
+call rpc#host#RegisterPlugin('legacy-python-provider', s:plugin_path, [])
+" Ensure that we can load the python host before bootstrapping
+try
+ let s:host = rpc#host#Require('legacy-python-provider')
+catch
+ echomsg v:exception
+ finish
+endtry
+
+let s:rpcrequest = function('rpcrequest')
+
+function! provider#python#Call(method, args)
+ return call(s:rpcrequest, insert(insert(a:args, 'python_'.a:method), s:host))
+endfunction
diff --git a/runtime/autoload/provider/script_host.py b/runtime/autoload/provider/script_host.py
new file mode 100644
index 0000000000..14208310aa
--- /dev/null
+++ b/runtime/autoload/provider/script_host.py
@@ -0,0 +1,243 @@
+"""Legacy python-vim emulation."""
+import imp
+import logging
+import os
+import sys
+
+import neovim
+
+__all__ = ('ScriptHost',)
+
+
+logger = logging.getLogger(__name__)
+debug, info, warn = (logger.debug, logger.info, logger.warn,)
+
+IS_PYTHON3 = sys.version_info >= (3, 0)
+
+if IS_PYTHON3:
+ basestring = str
+
+
+@neovim.plugin
+class ScriptHost(object):
+
+ """Provides an environment for running python plugins created for Vim."""
+
+ def __init__(self, nvim):
+ """Initialize the legacy python-vim environment."""
+ self.setup(nvim)
+ # context where all code will run
+ self.module = imp.new_module('__main__')
+ nvim.script_context = self.module
+ # it seems some plugins assume 'sys' is already imported, so do it now
+ exec('import sys', self.module.__dict__)
+ self.legacy_vim = nvim.with_hook(LegacyEvalHook())
+ if IS_PYTHON3:
+ self.legacy_vim = self.legacy_vim.with_hook(
+ neovim.DecodeHook(
+ encoding=nvim.options['encoding'].decode('ascii')))
+ sys.modules['vim'] = self.legacy_vim
+
+ def setup(self, nvim):
+ """Setup import hooks and global streams.
+
+ This will add import hooks for importing modules from runtime
+ directories and patch the sys module so 'print' calls will be
+ forwarded to Nvim.
+ """
+ self.nvim = nvim
+ info('install import hook/path')
+ self.hook = path_hook(nvim)
+ sys.path_hooks.append(self.hook)
+ nvim.VIM_SPECIAL_PATH = '_vim_path_'
+ sys.path.append(nvim.VIM_SPECIAL_PATH)
+ info('redirect sys.stdout and sys.stderr')
+ self.saved_stdout = sys.stdout
+ self.saved_stderr = sys.stderr
+ sys.stdout = RedirectStream(lambda data: nvim.out_write(data))
+ sys.stderr = RedirectStream(lambda data: nvim.err_write(data))
+
+ def teardown(self):
+ """Restore state modified from the `setup` call."""
+ for plugin in self.installed_plugins:
+ if hasattr(plugin, 'on_teardown'):
+ plugin.teardown()
+ nvim = self.nvim
+ info('uninstall import hook/path')
+ sys.path.remove(nvim.VIM_SPECIAL_PATH)
+ sys.path_hooks.remove(self.hook)
+ info('restore sys.stdout and sys.stderr')
+ sys.stdout = self.saved_stdout
+ sys.stderr = self.saved_stderr
+
+ @neovim.rpc_export('python_execute', sync=True)
+ def python_execute(self, script, range_start, range_stop):
+ """Handle the `python` ex command."""
+ self._set_current_range(range_start, range_stop)
+ exec(script, self.module.__dict__)
+
+ @neovim.rpc_export('python_execute_file', sync=True)
+ def python_execute_file(self, file_path, range_start, range_stop):
+ """Handle the `pyfile` ex command."""
+ self._set_current_range(range_start, range_stop)
+ with open(file_path) as f:
+ script = compile(f.read(), file_path, 'exec')
+ exec(script, self.module.__dict__)
+
+ @neovim.rpc_export('python_do_range', sync=True)
+ def python_do_range(self, start, stop, code):
+ """Handle the `pydo` ex command."""
+ self._set_current_range(start, stop)
+ nvim = self.nvim
+ start -= 1
+ stop -= 1
+ fname = '_vim_pydo'
+
+ # Python3 code (exec) must be a string, mixing bytes with
+ # function_def would use bytes.__repr__ instead
+ if isinstance and isinstance(code, bytes):
+ code = code.decode(nvim.options['encoding'].decode('ascii'))
+ # define the function
+ function_def = 'def %s(line, linenr):\n %s' % (fname, code,)
+ exec(function_def, self.module.__dict__)
+ # get the function
+ function = self.module.__dict__[fname]
+ while start <= stop:
+ # Process batches of 5000 to avoid the overhead of making multiple
+ # API calls for every line. Assuming an average line length of 100
+ # bytes, approximately 488 kilobytes will be transferred per batch,
+ # which can be done very quickly in a single API call.
+ sstart = start
+ sstop = min(start + 5000, stop)
+ lines = nvim.current.buffer.get_line_slice(sstart, sstop, True,
+ True)
+
+ exception = None
+ newlines = []
+ linenr = sstart + 1
+ for i, line in enumerate(lines):
+ result = function(line, linenr)
+ if result is None:
+ # Update earlier lines, and skip to the next
+ if newlines:
+ end = sstart + len(newlines) - 1
+ nvim.current.buffer.set_line_slice(sstart, end,
+ True, True,
+ newlines)
+ sstart += len(newlines) + 1
+ newlines = []
+ pass
+ elif isinstance(result, basestring):
+ newlines.append(result)
+ else:
+ exception = TypeError('pydo should return a string ' +
+ 'or None, found %s instead'
+ % result.__class__.__name__)
+ break
+ linenr += 1
+
+ start = sstop + 1
+ if newlines:
+ end = sstart + len(newlines) - 1
+ nvim.current.buffer.set_line_slice(sstart, end, True, True,
+ newlines)
+ if exception:
+ raise exception
+ # delete the function
+ del self.module.__dict__[fname]
+
+ @neovim.rpc_export('python_eval', sync=True)
+ def python_eval(self, expr):
+ """Handle the `pyeval` vim function."""
+ return eval(expr, self.module.__dict__)
+
+ def _set_current_range(self, start, stop):
+ current = self.legacy_vim.current
+ current.range = current.buffer.range(start, stop)
+
+
+class RedirectStream(object):
+ def __init__(self, redirect_handler):
+ self.redirect_handler = redirect_handler
+
+ def write(self, data):
+ self.redirect_handler(data)
+
+ def writelines(self, seq):
+ self.redirect_handler('\n'.join(seq))
+
+
+class LegacyEvalHook(neovim.SessionHook):
+
+ """Injects legacy `vim.eval` behavior to a Nvim instance."""
+
+ def __init__(self):
+ super(LegacyEvalHook, self).__init__(from_nvim=self._string_eval)
+
+ def _string_eval(self, obj, session, method, kind):
+ if method == 'vim_eval' and isinstance(obj, (int, long, float)):
+ return str(obj)
+ return obj
+
+
+# This was copied/adapted from nvim-python help
+def path_hook(nvim):
+ def _get_paths():
+ return discover_runtime_directories(nvim)
+
+ def _find_module(fullname, oldtail, path):
+ idx = oldtail.find('.')
+ if idx > 0:
+ name = oldtail[:idx]
+ tail = oldtail[idx+1:]
+ fmr = imp.find_module(name, path)
+ module = imp.load_module(fullname[:-len(oldtail)] + name, *fmr)
+ return _find_module(fullname, tail, module.__path__)
+ else:
+ fmr = imp.find_module(fullname, path)
+ return imp.load_module(fullname, *fmr)
+
+ class VimModuleLoader(object):
+ def __init__(self, module):
+ self.module = module
+
+ def load_module(self, fullname, path=None):
+ return self.module
+
+ class VimPathFinder(object):
+ @classmethod
+ def find_module(cls, fullname, path=None):
+ try:
+ return VimModuleLoader(
+ _find_module(fullname, fullname, path or _get_paths()))
+ except ImportError:
+ return None
+
+ @classmethod
+ def load_module(cls, fullname, path=None):
+ return _find_module(fullname, fullname, path or _get_paths())
+
+ def hook(path):
+ if path == nvim.VIM_SPECIAL_PATH:
+ return VimPathFinder
+ else:
+ raise ImportError
+
+ return hook
+
+
+def discover_runtime_directories(nvim):
+ rv = []
+ for path in nvim.list_runtime_paths():
+ if not os.path.exists(path):
+ continue
+ path1 = os.path.join(path, b'pythonx')
+ if IS_PYTHON3:
+ path2 = os.path.join(path, b'python3')
+ else:
+ path2 = os.path.join(path, b'python2')
+ if os.path.exists(path1):
+ rv.append(path1)
+ if os.path.exists(path2):
+ rv.append(path2)
+ return rv
diff --git a/runtime/autoload/rpc/define.vim b/runtime/autoload/rpc/define.vim
new file mode 100644
index 0000000000..e7817c5fac
--- /dev/null
+++ b/runtime/autoload/rpc/define.vim
@@ -0,0 +1,248 @@
+function! rpc#define#CommandOnHost(host, method, sync, name, opts)
+ let prefix = ''
+
+ if has_key(a:opts, 'range')
+ if a:opts.range == '' || a:opts.range == '%'
+ " -range or -range=%, pass the line range in a list
+ let prefix = '<line1>,<line2>'
+ elseif matchstr(a:opts.range, '\d') != ''
+ " -range=N, pass the count
+ let prefix = '<count>'
+ endif
+ elseif has_key(a:opts, 'count')
+ let prefix = '<count>'
+ endif
+
+ let forward_args = [prefix.a:name]
+
+ if has_key(a:opts, 'bang')
+ call add(forward_args, '<bang>')
+ endif
+
+ if has_key(a:opts, 'register')
+ call add(forward_args, ' <register>')
+ endif
+
+ if has_key(a:opts, 'nargs')
+ call add(forward_args, ' <args>')
+ endif
+
+ exe s:GetCommandPrefix(a:name, a:opts)
+ \ .' call rpc#define#CommandBootstrap("'.a:host.'"'
+ \ . ', "'.a:method.'"'
+ \ . ', "'.a:sync.'"'
+ \ . ', "'.a:name.'"'
+ \ . ', '.string(a:opts).''
+ \ . ', "'.join(forward_args, '').'"'
+ \ . ')'
+endfunction
+
+
+function! rpc#define#CommandBootstrap(host, method, sync, name, opts, forward)
+ let channel = rpc#host#Require(a:host)
+
+ if channel
+ call rpc#define#CommandOnChannel(channel, a:method, a:sync, a:name, a:opts)
+ exe a:forward
+ else
+ exe 'delcommand '.a:name
+ echoerr 'Host "'a:host.'" is not available, deleting command "'.a:name.'"'
+ endif
+endfunction
+
+
+function! rpc#define#CommandOnChannel(channel, method, sync, name, opts)
+ let rpcargs = [a:channel, '"'.a:method.'"']
+ if has_key(a:opts, 'nargs')
+ " -nargs, pass arguments in a list
+ call add(rpcargs, '[<f-args>]')
+ endif
+
+ if has_key(a:opts, 'range')
+ if a:opts.range == '' || a:opts.range == '%'
+ " -range or -range=%, pass the line range in a list
+ call add(rpcargs, '[<line1>, <line2>]')
+ elseif matchstr(a:opts.range, '\d') != ''
+ " -range=N, pass the count
+ call add(rpcargs, '<count>')
+ endif
+ elseif has_key(a:opts, 'count')
+ " count
+ call add(rpcargs, '<count>')
+ endif
+
+ if has_key(a:opts, 'bang')
+ " bang
+ call add(rpcargs, '<q-bang> == "!"')
+ endif
+
+ if has_key(a:opts, 'register')
+ " register
+ call add(rpcargs, '<q-reg>')
+ endif
+
+ call s:AddEval(rpcargs, a:opts)
+ exe s:GetCommandPrefix(a:name, a:opts)
+ \ . ' call '.s:GetRpcFunction(a:sync).'('.join(rpcargs, ', ').')'
+endfunction
+
+
+function! rpc#define#AutocmdOnHost(host, method, sync, name, opts)
+ let group = s:GetNextAutocmdGroup()
+ let forward = '"doau '.group.' '.a:name.' ".'.'expand("<amatch>")'
+ let a:opts.group = group
+ let bootstrap_def = s:GetAutocmdPrefix(a:name, a:opts)
+ \ .' call rpc#define#AutocmdBootstrap("'.a:host.'"'
+ \ . ', "'.a:method.'"'
+ \ . ', "'.a:sync.'"'
+ \ . ', "'.a:name.'"'
+ \ . ', '.string(a:opts).''
+ \ . ', "'.escape(forward, '"').'"'
+ \ . ')'
+ exe bootstrap_def
+endfunction
+
+
+function! rpc#define#AutocmdBootstrap(host, method, sync, name, opts, forward)
+ let channel = rpc#host#Require(a:host)
+
+ exe 'autocmd! '.a:opts.group
+ if channel
+ call rpc#define#AutocmdOnChannel(channel, a:method, a:sync, a:name,
+ \ a:opts)
+ exe eval(a:forward)
+ else
+ exe 'augroup! '.a:opts.group
+ echoerr 'Host "'a:host.'" for "'.a:name.'" autocmd is not available'
+ endif
+endfunction
+
+
+function! rpc#define#AutocmdOnChannel(channel, method, sync, name, opts)
+ let rpcargs = [a:channel, '"'.a:method.'"']
+ call s:AddEval(rpcargs, a:opts)
+
+ let autocmd_def = s:GetAutocmdPrefix(a:name, a:opts)
+ \ . ' call '.s:GetRpcFunction(a:sync).'('.join(rpcargs, ', ').')'
+ exe autocmd_def
+endfunction
+
+
+function! rpc#define#FunctionOnHost(host, method, sync, name, opts)
+ let group = s:GetNextAutocmdGroup()
+ exe 'autocmd! '.group.' FuncUndefined '.a:name
+ \ .' call rpc#define#FunctionBootstrap("'.a:host.'"'
+ \ . ', "'.a:method.'"'
+ \ . ', "'.a:sync.'"'
+ \ . ', "'.a:name.'"'
+ \ . ', '.string(a:opts)
+ \ . ', "'.group.'"'
+ \ . ')'
+endfunction
+
+
+function! rpc#define#FunctionBootstrap(host, method, sync, name, opts, group)
+ let channel = rpc#host#Require(a:host)
+
+ exe 'autocmd! '.a:group
+ exe 'augroup! '.a:group
+ if channel
+ call rpc#define#FunctionOnChannel(channel, a:method, a:sync, a:name,
+ \ a:opts)
+ else
+ echoerr 'Host "'a:host.'" for "'.a:name.'" function is not available'
+ endif
+endfunction
+
+
+function! rpc#define#FunctionOnChannel(channel, method, sync, name, opts)
+ let rpcargs = [a:channel, '"'.a:method.'"', 'a:000']
+ call s:AddEval(rpcargs, a:opts)
+
+ let function_def = s:GetFunctionPrefix(a:name, a:opts)
+ \ . 'return '.s:GetRpcFunction(a:sync).'('.join(rpcargs, ', ').')'
+ \ . "\nendfunction"
+ exe function_def
+endfunction
+
+
+function! s:GetRpcFunction(sync)
+ if a:sync
+ return 'rpcrequest'
+ endif
+ return 'rpcnotify'
+endfunction
+
+
+function! s:GetCommandPrefix(name, opts)
+ return 'command!'.s:StringifyOpts(a:opts, ['nargs', 'complete', 'range',
+ \ 'count', 'bang', 'bar', 'register']).' '.a:name
+endfunction
+
+
+" Each msgpack-rpc autocommand has it's own unique group, which is derived
+" from an autoincrementing gid(group id). This is required for replacing the
+" autocmd implementation with the lazy-load mechanism
+let s:next_gid = 1
+function! s:GetNextAutocmdGroup()
+ let gid = s:next_gid
+ let s:next_gid += 1
+
+ let group_name = 'RPC_DEFINE_AUTOCMD_GROUP_'.gid
+ " Ensure the group is defined
+ exe 'augroup '.group_name.' | augroup END'
+ return group_name
+endfunction
+
+
+function! s:GetAutocmdPrefix(name, opts)
+ if has_key(a:opts, 'group')
+ let group = a:opts.group
+ else
+ let group = s:GetNextAutocmdGroup()
+ endif
+ let rv = ['autocmd!', group, a:name]
+
+ if has_key(a:opts, 'pattern')
+ call add(rv, a:opts.pattern)
+ else
+ call add(rv, '*')
+ endif
+
+ if has_key(a:opts, 'nested') && a:opts.nested
+ call add(rv, 'nested')
+ endif
+
+ return join(rv, ' ')
+endfunction
+
+
+function! s:GetFunctionPrefix(name, opts)
+ return "function! ".a:name."(...)\n"
+endfunction
+
+
+function! s:StringifyOpts(opts, keys)
+ let rv = []
+ for key in a:keys
+ if has_key(a:opts, key)
+ call add(rv, ' -'.key)
+ let val = a:opts[key]
+ if type(val) != type('') || val != ''
+ call add(rv, '='.val)
+ endif
+ endif
+ endfor
+ return join(rv, '')
+endfunction
+
+
+function! s:AddEval(rpcargs, opts)
+ if has_key(a:opts, 'eval')
+ if type(a:opts.eval) != type('') || a:opts.eval == ''
+ throw "Eval option must be a non-empty string"
+ endif
+ " evaluate an expression and pass as argument
+ call add(a:rpcargs, 'eval("'.escape(a:opts.eval, '"').'")')
+ endif
+endfunction
diff --git a/runtime/autoload/rpc/host.vim b/runtime/autoload/rpc/host.vim
new file mode 100644
index 0000000000..177d816df0
--- /dev/null
+++ b/runtime/autoload/rpc/host.vim
@@ -0,0 +1,242 @@
+let s:hosts = {}
+let s:plugin_patterns = {
+ \ 'python': '*.py'
+ \ }
+let s:external_plugins = fnamemodify($MYVIMRC, ':p:h').'/.external_plugins~'
+
+
+" Register a host by associating it with a factory(funcref)
+function! rpc#host#Register(name, factory)
+ let s:hosts[a:name] = {'factory': a:factory, 'channel': 0, 'initialized': 0}
+ if type(a:factory) == type(1) && a:factory
+ " Passed a channel directly
+ let s:hosts[a:name].channel = a:factory
+ endif
+endfunction
+
+
+" Register a clone to an existing host. The new host will use the same factory
+" as `source`, but it will run as a different process. This can be used by
+" plugins that should run isolated from other plugins created for the same host
+" type
+function! rpc#host#RegisterClone(name, orig_name)
+ if !has_key(s:hosts, a:orig_name)
+ throw 'No host named "'.a:orig_name.'" is registered'
+ endif
+ let Factory = s:hosts[a:orig_name].factory
+ let s:hosts[a:name] = {'factory': Factory, 'channel': 0, 'initialized': 0}
+endfunction
+
+
+" Get a host channel, bootstrapping it if necessary
+function! rpc#host#Require(name)
+ if !has_key(s:hosts, a:name)
+ throw 'No host named "'.a:name.'" is registered'
+ endif
+ let host = s:hosts[a:name]
+ if !host.channel && !host.initialized
+ let host.channel = call(host.factory, [a:name])
+ let host.initialized = 1
+ endif
+ return host.channel
+endfunction
+
+
+function! rpc#host#IsRunning(name)
+ if !has_key(s:hosts, a:name)
+ throw 'No host named "'.a:name.'" is registered'
+ endif
+ return s:hosts[a:name].channel != 0
+endfunction
+
+
+" Example of registering a python plugin with two commands(one async), one
+" autocmd(async) and one function(sync):
+"
+" let s:plugin_path = expand('<sfile>:p:h').'/nvim_plugin.py'
+" call rpc#host#RegisterPlugin('python', s:plugin_path, [
+" \ {'type': 'command', 'name': 'PyCmd', 'sync': 1, 'opts': {}},
+" \ {'type': 'command', 'name': 'PyAsyncCmd', 'sync': 0, 'opts': {'eval': 'cursor()'}},
+" \ {'type': 'autocmd', 'name': 'BufEnter', 'sync': 0, 'opts': {'eval': 'expand("<afile>")'}},
+" \ {'type': 'function', 'name': 'PyFunc', 'sync': 1, 'opts': {}}
+" \ ])
+"
+" The third item in a declaration is a boolean: non zero means the command,
+" autocommand or function will be executed synchronously with rpcrequest.
+function! rpc#host#RegisterPlugin(host, path, specs)
+ let plugins = s:PluginsForHost(a:host)
+
+ for plugin in plugins
+ if plugin.path == a:path
+ throw 'Plugin "'.a:path.'" is already registered'
+ endif
+ endfor
+
+ if rpc#host#IsRunning(a:host)
+ " For now we won't allow registration of plugins when the host is already
+ " running.
+ throw 'Host "'.a:host.'" is already running'
+ endif
+
+ for spec in a:specs
+ let type = spec.type
+ let name = spec.name
+ let sync = spec.sync
+ let opts = spec.opts
+ let rpc_method = a:path
+ if type == 'command'
+ let rpc_method .= ':command:'.name
+ call rpc#define#CommandOnHost(a:host, rpc_method, sync, name, opts)
+ elseif type == 'autocmd'
+ " Since multiple handlers can be attached to the same autocmd event by a
+ " single plugin, we need a way to uniquely identify the rpc method to
+ " call. The solution is to append the autocmd pattern to the method
+ " name(This still has a limit: one handler per event/pattern combo, but
+ " there's no need to allow plugins define multiple handlers in that case)
+ let rpc_method .= ':autocmd:'.name.':'.get(opts, 'pattern', '*')
+ call rpc#define#AutocmdOnHost(a:host, rpc_method, sync, name, opts)
+ elseif type == 'function'
+ let rpc_method .= ':function:'.name
+ call rpc#define#FunctionOnHost(a:host, rpc_method, sync, name, opts)
+ else
+ echoerr 'Invalid declaration type: '.type
+ endif
+ endfor
+
+ call add(plugins, {'path': a:path, 'specs': a:specs})
+endfunction
+
+
+function! rpc#host#LoadExternalPlugins()
+ if filereadable(s:external_plugins)
+ exe 'source '.s:external_plugins
+ endif
+endfunction
+
+
+function! s:RegistrationCommands(host)
+ " Register a temporary host clone for discovering specs
+ let host_id = a:host.'-registration-clone'
+ call rpc#host#RegisterClone(host_id, a:host)
+ let pattern = s:plugin_patterns[a:host]
+ let paths = globpath(&rtp, 'plugin/external/'.a:host.'/'.pattern, 0, 1)
+ for path in paths
+ call rpc#host#RegisterPlugin(host_id, path, [])
+ endfor
+ let channel = rpc#host#Require(host_id)
+ let lines = []
+ for path in paths
+ let specs = rpcrequest(channel, 'specs', path)
+ call add(lines, "call rpc#host#RegisterPlugin('".a:host
+ \ ."', '".path."', [")
+ for spec in specs
+ call add(lines, " \\ ".string(spec).",")
+ endfor
+ call add(lines, " \\ ])")
+ endfor
+ " Delete the temporary host clone
+ call rpcstop(s:hosts[host_id].channel)
+ call remove(s:hosts, host_id)
+ call remove(s:plugins_for_host, host_id)
+ return lines
+endfunction
+
+
+function! s:UpdateExternalPlugins()
+ let commands = []
+ let hosts = keys(s:hosts)
+ for host in hosts
+ if has_key(s:plugin_patterns, host)
+ let commands = commands
+ \ + ['" '.host.' plugins']
+ \ + s:RegistrationCommands(host)
+ \ + ['', '']
+ endif
+ endfor
+ call writefile(commands, s:external_plugins)
+endfunction
+
+
+command! UpdateExternalPlugins call s:UpdateExternalPlugins()
+
+
+let s:plugins_for_host = {}
+function! s:PluginsForHost(host)
+ if !has_key(s:plugins_for_host, a:host)
+ let s:plugins_for_host[a:host] = []
+ end
+ return s:plugins_for_host[a:host]
+endfunction
+
+
+" Registration of standard hosts
+
+" Python {{{
+function! s:RequirePythonHost(name)
+ " Python host arguments
+ let args = ['-c', 'import neovim; neovim.start_host()']
+
+ " Collect registered python plugins into args
+ let python_plugins = s:PluginsForHost(a:name)
+ for plugin in python_plugins
+ call add(args, plugin.path)
+ endfor
+
+ " Try loading a python host using `python_host_prog` or `python`
+ let python_host_prog = get(g:, 'python_host_prog', 'python')
+ try
+ let channel_id = rpcstart(python_host_prog, args)
+ if rpcrequest(channel_id, 'poll') == 'ok'
+ return channel_id
+ endif
+ catch
+ endtry
+
+ " Failed, try a little harder to find the correct interpreter or
+ " report a friendly error to user
+ let get_version =
+ \ ' -c "import sys; sys.stdout.write(str(sys.version_info[0]) + '.
+ \ '\".\" + str(sys.version_info[1]))"'
+
+ let supported = ['2.6', '2.7']
+
+ " To load the python host a python executable must be available
+ if exists('g:python_host_prog')
+ \ && executable(g:python_host_prog)
+ \ && index(supported, system(g:python_host_prog.get_version)) >= 0
+ let python_host_prog = g:python_host_prog
+ elseif executable('python')
+ \ && index(supported, system('python'.get_version)) >= 0
+ let python_host_prog = 'python'
+ elseif executable('python2')
+ \ && index(supported, system('python2'.get_version)) >= 0
+ " In some distros, python3 is the default python
+ let python_host_prog = 'python2'
+ else
+ throw 'No python interpreter found'
+ endif
+
+ " Make sure we pick correct python version on path.
+ let python_host_prog = exepath(python_host_prog)
+ let python_version = systemlist(python_host_prog . ' --version')[0]
+
+ " Execute python, import neovim and print a string. If import_result doesn't
+ " matches the printed string, the user is missing the neovim module
+ let import_result = system(python_host_prog .
+ \ ' -c "import neovim, sys; sys.stdout.write(\"ok\")"')
+ if import_result != 'ok'
+ throw 'No neovim module found for ' . python_version
+ endif
+
+ try
+ let channel_id = rpcstart(python_host_prog, args)
+ if rpcrequest(channel_id, 'poll') == 'ok'
+ return channel_id
+ endif
+ catch
+ endtry
+ throw 'Failed to load python host'
+endfunction
+
+call rpc#host#Register('python', function('s:RequirePythonHost'))
+" }}}