diff options
-rw-r--r-- | runtime/autoload/provider/clipboard.vim | 22 | ||||
-rw-r--r-- | runtime/autoload/provider/node.vim | 9 | ||||
-rw-r--r-- | runtime/autoload/provider/python.vim | 4 | ||||
-rw-r--r-- | runtime/autoload/provider/python3.vim | 4 | ||||
-rw-r--r-- | runtime/autoload/provider/ruby.vim | 8 | ||||
-rw-r--r-- | runtime/doc/develop.txt | 46 | ||||
-rw-r--r-- | src/nvim/eval.c | 36 | ||||
-rw-r--r-- | test/functional/fixtures/autoload/provider/brokencall.vim | 2 | ||||
-rw-r--r-- | test/functional/fixtures/autoload/provider/brokenenabled.vim | 4 | ||||
-rw-r--r-- | test/functional/fixtures/autoload/provider/clipboard.vim | 4 | ||||
-rw-r--r-- | test/functional/provider/provider_spec.lua | 16 |
11 files changed, 78 insertions, 77 deletions
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index e43f8fbb7a..9b79e5abec 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -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()) diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index 6413720605..dd2423fe2a 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -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', []) - diff --git a/runtime/autoload/provider/python.vim b/runtime/autoload/provider/python.vim index d65506a55a..7921701141 100644 --- a/runtime/autoload/provider/python.vim +++ b/runtime/autoload/provider/python.vim @@ -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 diff --git a/runtime/autoload/provider/python3.vim b/runtime/autoload/provider/python3.vim index 469611c7ce..67350e3753 100644 --- a/runtime/autoload/provider/python3.vim +++ b/runtime/autoload/provider/python3.vim @@ -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 diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim index df43dffa40..f9d4f2b885 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -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') diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 0f9e17e697..180612cf20 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -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* diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ed01ca9f99..6b7a359508 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -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]`. diff --git a/test/functional/fixtures/autoload/provider/brokencall.vim b/test/functional/fixtures/autoload/provider/brokencall.vim index 2c83dd2b4a..7dc5d828d2 100644 --- a/test/functional/fixtures/autoload/provider/brokencall.vim +++ b/test/functional/fixtures/autoload/provider/brokencall.vim @@ -1,2 +1,2 @@ " A dummy test provider -let g:provider#brokencall#enabled = 1 +let g:loaded_brokencall_provider = 1 diff --git a/test/functional/fixtures/autoload/provider/brokenenabled.vim b/test/functional/fixtures/autoload/provider/brokenenabled.vim index 54ed11cedc..dd33ce66f3 100644 --- a/test/functional/fixtures/autoload/provider/brokenenabled.vim +++ b/test/functional/fixtures/autoload/provider/brokenenabled.vim @@ -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 diff --git a/test/functional/fixtures/autoload/provider/clipboard.vim b/test/functional/fixtures/autoload/provider/clipboard.vim index d1ddd81b12..efa9f82bd4 100644 --- a/test/functional/fixtures/autoload/provider/clipboard.vim +++ b/test/functional/fixtures/autoload/provider/clipboard.vim @@ -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 diff --git a/test/functional/provider/provider_spec.lua b/test/functional/provider/provider_spec.lua index 6f414f36f4..b690cf2566 100644 --- a/test/functional/provider/provider_spec.lua +++ b/test/functional/provider/provider_spec.lua @@ -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) |