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 clipboard provider uses shell commands to communicate with the clipboard.
" The provider function will only be registered if a supported command is " The provider function will only be registered if a supported command is
" available. " 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:copy = {}
let s:paste = {} let s:paste = {}
let s:clipboard = {} let s:clipboard = {}
@ -48,9 +58,6 @@ endfunction
let s:cache_enabled = 1 let s:cache_enabled = 1
let s:err = '' let s:err = ''
" eval_has_provider checks the variable to verify provider status
let g:provider#clipboard#enabled = 0
function! provider#clipboard#Error() abort function! provider#clipboard#Error() abort
return s:err return s:err
endfunction endfunction
@ -123,12 +130,6 @@ function! provider#clipboard#Executable() abort
return '' return ''
endfunction 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 function! s:clipboard.get(reg) abort
if type(s:paste[a:reg]) == v:t_func if type(s:paste[a:reg]) == v:t_func
return s:paste[a:reg]() return s:paste[a:reg]()
@ -195,4 +196,5 @@ function! provider#clipboard#Call(method, args) abort
endtry endtry
endfunction 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') if exists('g:loaded_node_provider')
finish finish
endif endif
let g:loaded_node_provider = 1 let g:loaded_node_provider = 0
let g:provider#node#enabled = 0
function! s:is_minimum_version(version, min_major, min_minor) abort function! s:is_minimum_version(version, min_major, min_minor) abort
if empty(a:version) if empty(a:version)
@ -141,12 +140,10 @@ endfunction
let s:err = '' let s:err = ''
let s:prog = provider#node#Detect() 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' let s:err = 'Cannot find the "neovim" node package. Try :checkhealth'
else
let g:provider#node#enabled = 1
endif endif
call remote#host#RegisterPlugin('node-provider', 'node', []) call remote#host#RegisterPlugin('node-provider', 'node', [])

View File

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

View File

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

View File

@ -2,12 +2,11 @@
if exists('g:loaded_ruby_provider') if exists('g:loaded_ruby_provider')
finish finish
endif endif
let g:loaded_ruby_provider = 1 let g:loaded_ruby_provider = 0
function! provider#ruby#Detect() abort function! provider#ruby#Detect() abort
return s:prog return s:prog
endfunction endfunction
let g:provider#ruby#enabled = 0
function! provider#ruby#Prog() abort function! provider#ruby#Prog() abort
return s:prog return s:prog
@ -63,11 +62,10 @@ endfunction
let s:err = '' let s:err = ''
let s:prog = s:detect() let s:prog = s:detect()
let s:plugin_path = expand('<sfile>:p:h') . '/script_host.rb' 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' let s:err = 'Cannot find the neovim RubyGem. Try :checkhealth'
else
let g:provider#ruby#enabled = 1
endif endif
call remote#host#RegisterClone('legacy-ruby-provider', 'ruby') call remote#host#RegisterClone('legacy-ruby-provider', 'ruby')

View File

@ -84,12 +84,11 @@ Developer guidelines *dev-guidelines*
PROVIDERS *dev-provider* PROVIDERS *dev-provider*
A goal of Nvim is to allow extension of the editor without special knowledge A primary goal of Nvim is to allow extension of the editor without special
in the core. But some Vim components are too tightly coupled; in those cases knowledge in the core. Some core functions are delegated to "providers"
a "provider" hook is exposed. implemented as external scripts.
Consider two examples of integration with external systems that are Examples:
implemented in Vim and are now decoupled from Nvim core as providers:
1. In the Vim source code, clipboard logic accounts for more than 1k lines of 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 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 scripting is performed by an external host process implemented in ~2k lines
of Python. of Python.
Ideally we could implement Python and clipboard integration in pure vimscript The provider framework invokes VimL from C. It is composed of two functions
and without touching the C code. But this is infeasible without compromising in eval.c:
backwards compatibility with Vim; that's where providers help.
The provider framework helps call vimscript from C. It is composed of two - eval_call_provider(name, method, arguments): calls provider#{name}#Call
functions in eval.c:
- eval_call_provider(name, method, arguments): calls provider#(name)#Call
with the method and arguments. with the method and arguments.
- eval_has_provider(name): Checks if a provider is implemented. Returns true - eval_has_provider(name): Checks the `g:loaded_{name}_provider` variable
if the provider#(name)#enabled variable is not 0. Called by |has()| which must be set by the provider script to indicate whether it is enabled
(vimscript) to check if features are available. and working. Called by |has()| 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.
For example, the Python provider is implemented by the For example, the Python provider is implemented by the
autoload/provider/python.vim script; the variable provider#python#enabled is only "autoload/provider/python.vim" script, which sets `g:loaded_python_provider`
1 if a valid external Python host is found. That works well with the to TRUE only if a valid external Python host is found. Then `has("python")`
`has('python')` expression (normally used by Python plugins) because if the reflects whether Python support is working.
Python host isn't installed then the plugin will "think" it is running in
a Vim compiled without the "+python" feature. *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* DOCUMENTATION *dev-doc*

View File

@ -23968,27 +23968,35 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
return rettv; return rettv;
} }
/// Check if a named provider is enabled /// Checks if a named provider is enabled.
bool eval_has_provider(const char *provider) bool eval_has_provider(const char *provider)
{ {
char enabled_varname[256]; char buf[256];
int enabled_varname_len = snprintf(enabled_varname, sizeof(enabled_varname), int len;
"provider#%s#enabled", provider);
typval_T tv; 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) { // Get the g:loaded_xx_provider variable.
emsgf("Provider '%s' failed to set %s", provider, enabled_varname); 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]`. /// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`.

View File

@ -1,2 +1,2 @@
" A dummy test provider " 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 " Dummy test provider, missing this required variable:
" let g:provider#brokenenabled#enabled = 0 " let g:loaded_brokenenabled_provider = 0
function! provider#brokenenabled#Call(method, args) function! provider#brokenenabled#Call(method, args)
return 42 return 42

View File

@ -1,3 +1,5 @@
let g:loaded_clipboard_provider = 1
let g:test_clip = { '+': [''], '*': [''], } let g:test_clip = { '+': [''], '*': [''], }
let s:methods = {} let s:methods = {}
@ -35,8 +37,6 @@ function! s:methods.set(lines, regtype, reg)
let g:test_clip[a:reg] = [a:lines, a:regtype] let g:test_clip[a:reg] = [a:lines, a:regtype]
endfunction endfunction
let provider#clipboard#enabled = 1
function! provider#clipboard#Call(method, args) function! provider#clipboard#Call(method, args)
return call(s:methods[a:method],a:args,s:methods) return call(s:methods[a:method],a:args,s:methods)
endfunction endfunction

View File

@ -1,19 +1,21 @@
local helpers = require('test.functional.helpers')(after_each) 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() before_each(function()
clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp') clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp')
end) end)
it('must set the enabled variable or fail', function() it('must define g:loaded_xx_provider', function()
eq(42, eval("provider#brokenenabled#Call('dosomething', [])")) command('set loadplugins')
feed_command("call has('brokenenabled')") expect_err('Vim:provider: brokenenabled: missing required variable g:loaded_brokenenabled_provider',
eq(0, eval("has('brokenenabled')")) eval, "has('brokenenabled')")
end) end)
it('without Call() are enabled', function() it('without Call() but with g:loaded_xx_provider', function()
eq(1, eval("has('brokencall')")) eq(1, eval("has('brokencall')"))
end) end)
end) end)