From 6b17082d3cf1f2e6c9eddb4f5ec0154f0f7286b0 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 21 Nov 2014 10:07:59 -0300 Subject: runtime: Refer to plugins running outside Nvim as "remote plugins" - Rename autoload/rpc to autoload/remote - External plugins are now remote plugins - External plugins directory is "rplugin" --- runtime/autoload/provider/python.vim | 6 +- runtime/autoload/remote/define.vim | 248 +++++++++++++++++++++++++++++++++++ runtime/autoload/remote/host.vim | 243 ++++++++++++++++++++++++++++++++++ runtime/autoload/rpc/define.vim | 248 ----------------------------------- runtime/autoload/rpc/host.vim | 243 ---------------------------------- 5 files changed, 494 insertions(+), 494 deletions(-) create mode 100644 runtime/autoload/remote/define.vim create mode 100644 runtime/autoload/remote/host.vim delete mode 100644 runtime/autoload/rpc/define.vim delete mode 100644 runtime/autoload/rpc/host.vim (limited to 'runtime/autoload') diff --git a/runtime/autoload/provider/python.vim b/runtime/autoload/provider/python.vim index 9ca81c35f4..53b984dfe2 100644 --- a/runtime/autoload/provider/python.vim +++ b/runtime/autoload/provider/python.vim @@ -11,11 +11,11 @@ let s:loaded_python_provider = 1 let s:plugin_path = expand(':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, []) +call remote#host#RegisterClone('legacy-python-provider', 'python') +call remote#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') + let s:host = remote#host#Require('legacy-python-provider') catch echomsg v:exception finish diff --git a/runtime/autoload/remote/define.vim b/runtime/autoload/remote/define.vim new file mode 100644 index 0000000000..dd2482998d --- /dev/null +++ b/runtime/autoload/remote/define.vim @@ -0,0 +1,248 @@ +function! remote#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 = ',' + elseif matchstr(a:opts.range, '\d') != '' + " -range=N, pass the count + let prefix = '' + endif + elseif has_key(a:opts, 'count') + let prefix = '' + endif + + let forward_args = [prefix.a:name] + + if has_key(a:opts, 'bang') + call add(forward_args, '') + endif + + if has_key(a:opts, 'register') + call add(forward_args, ' ') + endif + + if has_key(a:opts, 'nargs') + call add(forward_args, ' ') + endif + + exe s:GetCommandPrefix(a:name, a:opts) + \ .' call remote#define#CommandBootstrap("'.a:host.'"' + \ . ', "'.a:method.'"' + \ . ', "'.a:sync.'"' + \ . ', "'.a:name.'"' + \ . ', '.string(a:opts).'' + \ . ', "'.join(forward_args, '').'"' + \ . ')' +endfunction + + +function! remote#define#CommandBootstrap(host, method, sync, name, opts, forward) + let channel = remote#host#Require(a:host) + + if channel + call remote#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! remote#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, '[]') + 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, '[, ]') + elseif matchstr(a:opts.range, '\d') != '' + " -range=N, pass the count + call add(rpcargs, '') + endif + elseif has_key(a:opts, 'count') + " count + call add(rpcargs, '') + endif + + if has_key(a:opts, 'bang') + " bang + call add(rpcargs, ' == "!"') + endif + + if has_key(a:opts, 'register') + " register + call add(rpcargs, '') + endif + + call s:AddEval(rpcargs, a:opts) + exe s:GetCommandPrefix(a:name, a:opts) + \ . ' call '.s:GetRpcFunction(a:sync).'('.join(rpcargs, ', ').')' +endfunction + + +function! remote#define#AutocmdOnHost(host, method, sync, name, opts) + let group = s:GetNextAutocmdGroup() + let forward = '"doau '.group.' '.a:name.' ".'.'expand("")' + let a:opts.group = group + let bootstrap_def = s:GetAutocmdPrefix(a:name, a:opts) + \ .' call remote#define#AutocmdBootstrap("'.a:host.'"' + \ . ', "'.a:method.'"' + \ . ', "'.a:sync.'"' + \ . ', "'.a:name.'"' + \ . ', '.string(a:opts).'' + \ . ', "'.escape(forward, '"').'"' + \ . ')' + exe bootstrap_def +endfunction + + +function! remote#define#AutocmdBootstrap(host, method, sync, name, opts, forward) + let channel = remote#host#Require(a:host) + + exe 'autocmd! '.a:opts.group + if channel + call remote#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! remote#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! remote#define#FunctionOnHost(host, method, sync, name, opts) + let group = s:GetNextAutocmdGroup() + exe 'autocmd! '.group.' FuncUndefined '.a:name + \ .' call remote#define#FunctionBootstrap("'.a:host.'"' + \ . ', "'.a:method.'"' + \ . ', "'.a:sync.'"' + \ . ', "'.a:name.'"' + \ . ', '.string(a:opts) + \ . ', "'.group.'"' + \ . ')' +endfunction + + +function! remote#define#FunctionBootstrap(host, method, sync, name, opts, group) + let channel = remote#host#Require(a:host) + + exe 'autocmd! '.a:group + exe 'augroup! '.a:group + if channel + call remote#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! remote#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/remote/host.vim b/runtime/autoload/remote/host.vim new file mode 100644 index 0000000000..54a8bb3c41 --- /dev/null +++ b/runtime/autoload/remote/host.vim @@ -0,0 +1,243 @@ +let s:hosts = {} +let s:plugin_patterns = { + \ 'python': '*.py' + \ } +let s:remote_plugins_manifest = fnamemodify($MYVIMRC, ':p:h') + \.'/.'.fnamemodify($MYVIMRC, ':t').'-rplugin~' + + +" Register a host by associating it with a factory(funcref) +function! remote#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! remote#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! remote#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! remote#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 remote#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! remote#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 remote#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 remote#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 remote#define#AutocmdOnHost(a:host, rpc_method, sync, name, opts) + elseif type == 'function' + let rpc_method .= ':function:'.name + call remote#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! remote#host#LoadRemotePlugins() + if filereadable(s:remote_plugins_manifest) + exe 'source '.s:remote_plugins_manifest + endif +endfunction + + +function! s:RegistrationCommands(host) + " Register a temporary host clone for discovering specs + let host_id = a:host.'-registration-clone' + call remote#host#RegisterClone(host_id, a:host) + let pattern = s:plugin_patterns[a:host] + let paths = globpath(&rtp, 'rplugin/'.a:host.'/'.pattern, 0, 1) + for path in paths + call remote#host#RegisterPlugin(host_id, path, []) + endfor + let channel = remote#host#Require(host_id) + let lines = [] + for path in paths + let specs = rpcrequest(channel, 'specs', path) + call add(lines, "call remote#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:UpdateRemotePlugins() + 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:remote_plugins_manifest) +endfunction + + +command! UpdateRemotePlugins call s:UpdateRemotePlugins() + + +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 remote#host#Register('python', function('s:RequirePythonHost')) +" }}} diff --git a/runtime/autoload/rpc/define.vim b/runtime/autoload/rpc/define.vim deleted file mode 100644 index e7817c5fac..0000000000 --- a/runtime/autoload/rpc/define.vim +++ /dev/null @@ -1,248 +0,0 @@ -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 = ',' - elseif matchstr(a:opts.range, '\d') != '' - " -range=N, pass the count - let prefix = '' - endif - elseif has_key(a:opts, 'count') - let prefix = '' - endif - - let forward_args = [prefix.a:name] - - if has_key(a:opts, 'bang') - call add(forward_args, '') - endif - - if has_key(a:opts, 'register') - call add(forward_args, ' ') - endif - - if has_key(a:opts, 'nargs') - call add(forward_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, '[]') - 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, '[, ]') - elseif matchstr(a:opts.range, '\d') != '' - " -range=N, pass the count - call add(rpcargs, '') - endif - elseif has_key(a:opts, 'count') - " count - call add(rpcargs, '') - endif - - if has_key(a:opts, 'bang') - " bang - call add(rpcargs, ' == "!"') - endif - - if has_key(a:opts, 'register') - " register - call add(rpcargs, '') - 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("")' - 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 deleted file mode 100644 index 9294dd92a8..0000000000 --- a/runtime/autoload/rpc/host.vim +++ /dev/null @@ -1,243 +0,0 @@ -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')) -" }}} -- cgit