let s:hosts = {} let s:plugin_patterns = { \ 'python': '*.py' \ } let s:external_plugins = fnamemodify($MYVIMRC, ':p:h') \.'/.'.fnamemodify($MYVIMRC, ':t').'-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(':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("")'}}, " \ {'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, 'external-plugin/'.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')) " }}}