provider: let providers decide their status

Instead of deciding provider status in eval_has_provider, move the
decision to the provider Vim scripts.

Previously, provider loading worked as follows:

1. eval_has_provider() verified provider availability by searching for
   the provider#providername#Call function and cached this verificaion as a static
   variable for some providers
2. providers short-circuited on loading to prevent the definition of the
   Call function (with the exception of the node provider that did not)

This commit changes the expected interface between nvim and its
providers to facilitate provider reloading, by splitting the
verification of the provider from the availability of the Call function.

eval_has_provider() now checks for a provider#providername#enabled
variable. It is up to the provider script to set this to 0 or 1
accordingly. eval_call_provider() remains unchanged.

All providers hosting a Call function were updated to respect this.

The clipboard provider now has a Reload function to reload the
provider.
This commit is contained in:
Rui Abreu Ferreira 2019-06-09 18:22:10 +01:00 committed by Justin M. Keyes
parent 2860453c4f
commit 2cfe4748e5
11 changed files with 65 additions and 59 deletions

View File

@ -48,6 +48,9 @@ endfunction
let s:cache_enabled = 1
let s:err = ''
" eval_has_provider checks the variable to verify provider status
let g:provider#clipboard#enabled = 0
function! provider#clipboard#Error() abort
return s:err
endfunction
@ -120,12 +123,11 @@ function! provider#clipboard#Executable() abort
return ''
endfunction
if empty(provider#clipboard#Executable())
" provider#clipboard#Call() *must not* be defined if the provider is broken.
" Otherwise eval_has_provider() thinks the clipboard provider is
" functioning, and eval_call_provider() will happily call it.
finish
endif
" Call this to setup/reload the provider
function! provider#clipboard#Reload()
" #enabled is used by eval_has_provider()
let g:provider#clipboard#enabled = !empty(provider#clipboard#Executable())
endfunction
function! s:clipboard.get(reg) abort
if type(s:paste[a:reg]) == v:t_func
@ -192,3 +194,5 @@ function! provider#clipboard#Call(method, args) abort
let s:here = v:false
endtry
endfunction
call provider#clipboard#Reload()

View File

@ -2,6 +2,7 @@ if exists('g:loaded_node_provider')
finish
endif
let g:loaded_node_provider = 1
let g:provider#node#enabled = 0
function! s:is_minimum_version(version, min_major, min_minor) abort
if empty(a:version)
@ -143,6 +144,9 @@ let s:prog = provider#node#Detect()
if empty(s:prog)
let s:err = 'Cannot find the "neovim" node package. Try :checkhealth'
else
let g:provider#node#enabled = 1
endif
call remote#host#RegisterPlugin('node-provider', 'node', [])

View File

@ -10,6 +10,7 @@ endif
let g:loaded_python_provider = 1
let [s:prog, s:err] = provider#pythonx#Detect(2)
let g:provider#python#enabled = !empty(s:prog)
function! provider#python#Prog() abort
return s:prog
@ -19,11 +20,6 @@ function! provider#python#Error() abort
return s:err
endfunction
if s:prog == ''
" Detection failed
finish
endif
" The Python provider plugin will run in a separate instance of the Python
" host.
call remote#host#RegisterClone('legacy-python-provider', 'python')

View File

@ -10,6 +10,7 @@ endif
let g:loaded_python3_provider = 1
let [s:prog, s:err] = provider#pythonx#Detect(3)
let g:provider#python3#enabled = !empty(s:prog)
function! provider#python3#Prog() abort
return s:prog
@ -19,11 +20,6 @@ function! provider#python3#Error() abort
return s:err
endfunction
if s:prog == ''
" Detection failed
finish
endif
" The Python3 provider plugin will run in a separate instance of the Python3
" host.
call remote#host#RegisterClone('legacy-python3-provider', 'python3')

View File

@ -7,6 +7,7 @@ let g:loaded_ruby_provider = 1
function! provider#ruby#Detect() abort
return s:prog
endfunction
let g:provider#ruby#enabled = 0
function! provider#ruby#Prog() abort
return s:prog
@ -65,6 +66,8 @@ let s:plugin_path = expand('<sfile>:p:h') . '/script_host.rb'
if empty(s:prog)
let s:err = 'Cannot find the neovim RubyGem. Try :checkhealth'
else
let g:provider#ruby#enabled = 1
endif
call remote#host#RegisterClone('legacy-ruby-provider', 'ruby')

View File

@ -111,7 +111,7 @@ functions in eval.c:
- eval_call_provider(name, method, arguments): calls provider#(name)#Call
with the method and arguments.
- eval_has_provider(name): Checks if a provider is implemented. Returns true
if the provider#(name)#Call function is implemented. Called by |has()|
if the provider#(name)#enabled variable is not 0. Called by |has()|
(vimscript) to check if features are available.
The provider#(name)#Call function implements integration with an external
@ -119,8 +119,8 @@ system, because shell commands and |RPC| clients are easier to work with in
vimscript.
For example, the Python provider is implemented by the
autoload/provider/python.vim script; the provider#python#Call function is only
defined if a valid external Python host is found. That works well with the
autoload/provider/python.vim script; the variable provider#python#enabled is only
1 if a valid external Python host is found. That works well with the
`has('python')` expression (normally used by Python plugins) because if the
Python host isn't installed then the plugin will "think" it is running in
a Vim compiled without the "+python" feature.

View File

@ -23968,52 +23968,27 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
return rettv;
}
bool eval_has_provider(const char *name)
/// Check if a named provider is enabled
bool eval_has_provider(const char *provider)
{
#define CHECK_PROVIDER(name) \
if (has_##name == -1) { \
has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \
if (!has_##name) { \
script_autoload("provider#" #name "#Call", \
sizeof("provider#" #name "#Call") - 1, \
false); \
has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \
} \
}
char enabled_varname[256];
int enabled_varname_len = snprintf(enabled_varname, sizeof(enabled_varname),
"provider#%s#enabled", provider);
static int has_clipboard = -1;
static int has_python = -1;
static int has_python3 = -1;
static int has_ruby = -1;
typval_T args[1];
args[0].v_type = VAR_UNKNOWN;
typval_T tv;
if (get_var_tv(enabled_varname, enabled_varname_len, &tv,
NULL, false, false) == FAIL) {
char call_varname[256];
snprintf(call_varname, sizeof(call_varname), "provider#%s#Call", provider);
int has_call = !!find_func((char_u *)call_varname);
if (strequal(name, "clipboard")) {
CHECK_PROVIDER(clipboard);
return has_clipboard;
} else if (strequal(name, "python3")) {
CHECK_PROVIDER(python3);
return has_python3;
} else if (strequal(name, "python")) {
CHECK_PROVIDER(python);
return has_python;
} else if (strequal(name, "ruby")) {
bool need_check_ruby = (has_ruby == -1);
CHECK_PROVIDER(ruby);
if (need_check_ruby && has_ruby == 1) {
char *rubyhost = call_func_retstr("provider#ruby#Detect", 0, args, true);
if (rubyhost) {
if (*rubyhost == NUL) {
// Invalid rubyhost executable. Gem is probably not installed.
has_ruby = 0;
}
xfree(rubyhost);
}
if (has_call && p_lpl) {
emsgf("Provider '%s' failed to set %s", provider, enabled_varname);
}
return has_ruby;
return false;
}
return false;
return (tv.v_type == VAR_NUMBER) ? tv.vval.v_number != 0: true;
}
/// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`.

View File

@ -0,0 +1,2 @@
" A dummy test provider
let g:provider#brokencall#enabled = 1

View File

@ -0,0 +1,6 @@
" Dummy test provider, missing
" let g:provider#brokenenabled#enabled = 0
function! provider#brokenenabled#Call(method, args)
return 42
endfunction

View File

@ -35,6 +35,7 @@ function! s:methods.set(lines, regtype, reg)
let g:test_clip[a:reg] = [a:lines, a:regtype]
endfunction
let provider#clipboard#enabled = 1
function! provider#clipboard#Call(method, args)
return call(s:methods[a:method],a:args,s:methods)

View File

@ -0,0 +1,19 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, feed_command, eval = helpers.clear, helpers.eq, helpers.feed_command, helpers.eval
describe('Providers', function()
before_each(function()
clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp')
end)
it('must set the enabled variable or fail', function()
eq(42, eval("provider#brokenenabled#Call('dosomething', [])"))
feed_command("call has('brokenenabled')")
eq(0, eval("has('brokenenabled')"))
end)
it('without Call() are enabled', function()
eq(1, eval("has('brokencall')"))
end)
end)