provider: decide status by g:loaded_xx_provider

This commit is contained in:
Justin M. Keyes 2019-08-04 03:54:06 +02:00
parent 2cfe4748e5
commit 66938b928c
11 changed files with 78 additions and 77 deletions

View File

@ -1,6 +1,16 @@
" The clipboard provider uses shell commands to communicate with the clipboard.
" The provider function will only be registered if a supported command is
" available.
if exists('g:loaded_clipboard_provider')
finish
endif
" Default to FALSE. Set by provider#clipboard#Executable() later.
" To force a reload:
" :unlet g:loaded_clipboard_provider
" :runtime autoload/provider/clipboard.vim
let g:loaded_clipboard_provider = 0
let s:copy = {}
let s:paste = {}
let s:clipboard = {}
@ -48,9 +58,6 @@ 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
@ -123,12 +130,6 @@ function! provider#clipboard#Executable() abort
return ''
endfunction
" 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
return s:paste[a:reg]()
@ -195,4 +196,5 @@ function! provider#clipboard#Call(method, args) abort
endtry
endfunction
call provider#clipboard#Reload()
" eval_has_provider() decides based on this variable.
let g:loaded_clipboard_provider = !empty(provider#clipboard#Executable())

View File

@ -1,8 +1,7 @@
if exists('g:loaded_node_provider')
finish
endif
let g:loaded_node_provider = 1
let g:provider#node#enabled = 0
let g:loaded_node_provider = 0
function! s:is_minimum_version(version, min_major, min_minor) abort
if empty(a:version)
@ -141,12 +140,10 @@ endfunction
let s:err = ''
let s:prog = provider#node#Detect()
let g:loaded_node_provider = !empty(s:prog)
if empty(s:prog)
if !g:loaded_node_provider
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

@ -7,10 +7,8 @@
if exists('g:loaded_python_provider')
finish
endif
let g:loaded_python_provider = 1
let [s:prog, s:err] = provider#pythonx#Detect(2)
let g:provider#python#enabled = !empty(s:prog)
let g:loaded_python_provider = !empty(s:prog)
function! provider#python#Prog() abort
return s:prog

View File

@ -7,10 +7,8 @@
if exists('g:loaded_python3_provider')
finish
endif
let g:loaded_python3_provider = 1
let [s:prog, s:err] = provider#pythonx#Detect(3)
let g:provider#python3#enabled = !empty(s:prog)
let g:loaded_python3_provider = !empty(s:prog)
function! provider#python3#Prog() abort
return s:prog

View File

@ -2,12 +2,11 @@
if exists('g:loaded_ruby_provider')
finish
endif
let g:loaded_ruby_provider = 1
let g:loaded_ruby_provider = 0
function! provider#ruby#Detect() abort
return s:prog
endfunction
let g:provider#ruby#enabled = 0
function! provider#ruby#Prog() abort
return s:prog
@ -63,11 +62,10 @@ endfunction
let s:err = ''
let s:prog = s:detect()
let s:plugin_path = expand('<sfile>:p:h') . '/script_host.rb'
let g:loaded_ruby_provider = !empty(s:prog)
if empty(s:prog)
if !g:loaded_ruby_provider
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

@ -84,12 +84,11 @@ Developer guidelines *dev-guidelines*
PROVIDERS *dev-provider*
A goal of Nvim is to allow extension of the editor without special knowledge
in the core. But some Vim components are too tightly coupled; in those cases
a "provider" hook is exposed.
A primary goal of Nvim is to allow extension of the editor without special
knowledge in the core. Some core functions are delegated to "providers"
implemented as external scripts.
Consider two examples of integration with external systems that are
implemented in Vim and are now decoupled from Nvim core as providers:
Examples:
1. In the Vim source code, clipboard logic accounts for more than 1k lines of
C source code (ui.c), to perform two tasks that are now accomplished with
@ -101,29 +100,28 @@ implemented in Vim and are now decoupled from Nvim core as providers:
scripting is performed by an external host process implemented in ~2k lines
of Python.
Ideally we could implement Python and clipboard integration in pure vimscript
and without touching the C code. But this is infeasible without compromising
backwards compatibility with Vim; that's where providers help.
The provider framework invokes VimL from C. It is composed of two functions
in eval.c:
The provider framework helps call vimscript from C. It is composed of two
functions in eval.c:
- eval_call_provider(name, method, arguments): calls provider#(name)#Call
- 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)#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
system, because shell commands and |RPC| clients are easier to work with in
vimscript.
- eval_has_provider(name): Checks the `g:loaded_{name}_provider` variable
which must be set by the provider script to indicate whether it is enabled
and working. Called by |has()| to check if features are available.
For example, the Python provider is implemented by 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.
"autoload/provider/python.vim" script, which sets `g:loaded_python_provider`
to TRUE only if a valid external Python host is found. Then `has("python")`
reflects whether Python support is working.
*provider-reload*
Sometimes a GUI or other application may want to force a provider to
"reload". To reload a provider, undefine its "loaded" flag, then use
|:runtime| to reload it: >
:unlet g:loaded_clipboard_provider
:runtime autoload/provider/clipboard.vim
DOCUMENTATION *dev-doc*

View File

@ -23968,27 +23968,35 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
return rettv;
}
/// Check if a named provider is enabled
/// Checks if a named provider is enabled.
bool eval_has_provider(const char *provider)
{
char enabled_varname[256];
int enabled_varname_len = snprintf(enabled_varname, sizeof(enabled_varname),
"provider#%s#enabled", provider);
char buf[256];
int len;
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 (has_call && p_lpl) {
emsgf("Provider '%s' failed to set %s", provider, enabled_varname);
// Get the g:loaded_xx_provider variable.
len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", provider);
if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) {
// Trigger autoload once.
len = snprintf(buf, sizeof(buf), "provider#%s#bogus", provider);
script_autoload(buf, len, false);
// Retry the (non-autoload-style) variable.
len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", provider);
if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) {
// Show a hint if Call() is defined but g:loaded_xx_provider is missing.
snprintf(buf, sizeof(buf), "provider#%s#Call", provider);
bool has_call = !!find_func((char_u *)buf);
if (has_call && p_lpl) {
emsgf("provider: %s: missing required variable g:loaded_%s_provider",
provider, provider);
}
return false;
}
return false;
}
return (tv.v_type == VAR_NUMBER) ? tv.vval.v_number != 0: true;
return (tv.v_type == VAR_NUMBER) ? !!tv.vval.v_number : false;
}
/// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`.

View File

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

View File

@ -1,5 +1,5 @@
" Dummy test provider, missing
" let g:provider#brokenenabled#enabled = 0
" Dummy test provider, missing this required variable:
" let g:loaded_brokenenabled_provider = 0
function! provider#brokenenabled#Call(method, args)
return 42

View File

@ -1,3 +1,5 @@
let g:loaded_clipboard_provider = 1
let g:test_clip = { '+': [''], '*': [''], }
let s:methods = {}
@ -35,8 +37,6 @@ 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)
endfunction

View File

@ -1,19 +1,21 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, feed_command, eval = helpers.clear, helpers.eq, helpers.feed_command, helpers.eval
local clear, eq, eval = helpers.clear, helpers.eq, helpers.eval
local command = helpers.command
local expect_err = helpers.expect_err
describe('Providers', function()
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')"))
it('must define g:loaded_xx_provider', function()
command('set loadplugins')
expect_err('Vim:provider: brokenenabled: missing required variable g:loaded_brokenenabled_provider',
eval, "has('brokenenabled')")
end)
it('without Call() are enabled', function()
it('without Call() but with g:loaded_xx_provider', function()
eq(1, eval("has('brokencall')"))
end)
end)