neovim/runtime/autoload/remote/host.vim
Justin M. Keyes 297677ecf4 remote/host.vim: Avoid "No matching autocommands".
:silent does not silence this message, even :redir does not consume it.
But execute() _does_ consume it, which interferes with the current
implementation of health.vim. It's prudent to avoid it in any case, even
if the implementation of health.vim changes in the future.
2016-08-21 22:03:28 -04:00

282 lines
8.6 KiB
VimL

let s:hosts = {}
let s:plugin_patterns = {}
let s:plugins_for_host = {}
" Register a host by associating it with a factory(funcref)
function! remote#host#Register(name, pattern, factory) abort
let s:hosts[a:name] = {'factory': a:factory, 'channel': 0, 'initialized': 0}
let s:plugin_patterns[a:name] = a:pattern
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) abort
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,
\ 'orig_name': a:orig_name
\ }
endfunction
" Get a host channel, bootstrapping it if necessary
function! remote#host#Require(name) abort
if empty(s:plugins_for_host)
call remote#host#LoadRemotePlugins()
endif
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_info = {
\ 'name': a:name,
\ 'orig_name': get(host, 'orig_name', a:name)
\ }
let host.channel = call(host.factory, [host_info])
let host.initialized = 1
endif
return host.channel
endfunction
function! remote#host#IsRunning(name) abort
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 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("<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! remote#host#RegisterPlugin(host, path, specs) abort
let plugins = remote#host#PluginsForHost(a:host)
for plugin in plugins
if plugin.path == a:path
throw 'Plugin "'.a:path.'" is already registered'
endif
endfor
if has_key(s:hosts, a:host) && 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
" Get the path to the rplugin manifest file.
function! s:GetManifestPath() abort
let manifest_base = ''
if exists('$NVIM_RPLUGIN_MANIFEST')
return fnamemodify($NVIM_RPLUGIN_MANIFEST, ':p')
endif
let dest = has('win32') ? '$LOCALAPPDATA' : '$XDG_DATA_HOME'
if !exists(dest)
let dest = has('win32') ? '~/AppData/Local' : '~/.local/share'
endif
let dest = fnamemodify(expand(dest), ':p')
if !empty(dest) && !filereadable(dest)
let dest .= ('/' ==# dest[-1:] ? '' : '/') . 'nvim'
call mkdir(dest, 'p', 0700)
let manifest_base = dest
endif
return manifest_base.'/rplugin.vim'
endfunction
" Old manifest file based on known script locations.
function! s:GetOldManifestPath() abort
let prefix = exists('$MYVIMRC')
\ ? $MYVIMRC
\ : matchstr(get(split(execute('scriptnames'), '\n'), 0, ''), '\f\+$')
return fnamemodify(expand(prefix, 1), ':h')
\.'/.'.fnamemodify(prefix, ':t').'-rplugin~'
endfunction
function! s:GetManifest() abort
let manifest = s:GetManifestPath()
if !filereadable(manifest)
" Check if an old manifest file exists and move it to the new location.
let old_manifest = s:GetOldManifestPath()
if filereadable(old_manifest)
call rename(old_manifest, manifest)
endif
endif
return manifest
endfunction
function! remote#host#LoadRemotePlugins() abort
let manifest = s:GetManifest()
if filereadable(manifest)
execute 'source' fnameescape(manifest)
endif
endfunction
function! remote#host#LoadRemotePluginsEvent(event, pattern) abort
autocmd! nvim-rplugin
call remote#host#LoadRemotePlugins()
if exists('#'.a:event.'#'.a:pattern) " Avoid 'No matching autocommands'.
execute 'silent doautocmd <nomodeline>' a:event a:pattern
endif
endfunction
function! s:RegistrationCommands(host) abort
" 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)
let paths = map(paths, 'tr(v:val,"\\","/")') " Normalize slashes #4795
if empty(paths)
return []
endif
for path in paths
call remote#host#RegisterPlugin(host_id, path, [])
endfor
let channel = remote#host#Require(host_id)
let lines = []
let registered = []
for path in paths
unlet! specs
let specs = rpcrequest(channel, 'specs', path)
if type(specs) != type([])
" host didn't return a spec list, indicates a failure while loading a
" plugin
continue
endif
call add(lines, "call remote#host#RegisterPlugin('".a:host
\ ."', '".path."', [")
for spec in specs
call add(lines, " \\ ".string(spec).",")
endfor
call add(lines, " \\ ])")
call add(registered, path)
endfor
echomsg printf("remote/host: %s host registered plugins %s",
\ a:host, string(map(registered, "fnamemodify(v:val, ':t')")))
" 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! remote#host#UpdateRemotePlugins() abort
let commands = []
let hosts = keys(s:hosts)
for host in hosts
if has_key(s:plugin_patterns, host)
try
let commands +=
\ ['" '.host.' plugins']
\ + s:RegistrationCommands(host)
\ + ['', '']
catch
echomsg v:throwpoint
echomsg v:exception
endtry
endif
endfor
call writefile(commands, s:GetManifest())
echomsg printf('remote/host: generated rplugin manifest: %s',
\ s:GetManifest())
endfunction
function! remote#host#PluginsForHost(host) abort
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
function! remote#host#LoadErrorForHost(host, log) abort
return 'Failed to load '. a:host . ' host. '.
\ 'You can try to see what happened '.
\ 'by starting Neovim with the environment variable '.
\ a:log . ' set to a file and opening the generated '.
\ 'log file. Also, the host stderr is available '.
\ 'in messages.'
endfunction
" Registration of standard hosts
" Python/Python3
call remote#host#Register('python', '*',
\ function('provider#pythonx#Require'))
call remote#host#Register('python3', '*',
\ function('provider#pythonx#Require'))
" Ruby
call remote#host#Register('ruby', '*.rb',
\ function('provider#ruby#Require'))