mirror of
https://github.com/neovim/neovim.git
synced 2024-12-25 05:35:10 -07:00
297677ecf4
: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.
282 lines
8.6 KiB
VimL
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'))
|