From 2cfe4748e57bd510b98ca81bd915f801f5a50bb5 Mon Sep 17 00:00:00 2001 From: Rui Abreu Ferreira Date: Sun, 9 Jun 2019 18:22:10 +0100 Subject: [PATCH 1/6] 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. --- runtime/autoload/provider/clipboard.vim | 16 ++++-- runtime/autoload/provider/node.vim | 4 ++ runtime/autoload/provider/python.vim | 6 +- runtime/autoload/provider/python3.vim | 6 +- runtime/autoload/provider/ruby.vim | 3 + runtime/doc/develop.txt | 6 +- src/nvim/eval.c | 55 +++++-------------- .../fixtures/autoload/provider/brokencall.vim | 2 + .../autoload/provider/brokenenabled.vim | 6 ++ .../fixtures/autoload/provider/clipboard.vim | 1 + test/functional/provider/provider_spec.lua | 19 +++++++ 11 files changed, 65 insertions(+), 59 deletions(-) create mode 100644 test/functional/fixtures/autoload/provider/brokencall.vim create mode 100644 test/functional/fixtures/autoload/provider/brokenenabled.vim create mode 100644 test/functional/provider/provider_spec.lua diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 2b06ee8c48..e43f8fbb7a 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -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() diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index 35882849bd..6413720605 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -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', []) + diff --git a/runtime/autoload/provider/python.vim b/runtime/autoload/provider/python.vim index a06cbe4814..d65506a55a 100644 --- a/runtime/autoload/provider/python.vim +++ b/runtime/autoload/provider/python.vim @@ -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') diff --git a/runtime/autoload/provider/python3.vim b/runtime/autoload/provider/python3.vim index 242a224cb3..469611c7ce 100644 --- a/runtime/autoload/provider/python3.vim +++ b/runtime/autoload/provider/python3.vim @@ -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') diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim index 3b4c6c4839..df43dffa40 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -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(':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') diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 843e23ee54..0f9e17e697 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -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. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index cefd351dd7..ed01ca9f99 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -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 ":" to `buf[bufsize]`. diff --git a/test/functional/fixtures/autoload/provider/brokencall.vim b/test/functional/fixtures/autoload/provider/brokencall.vim new file mode 100644 index 0000000000..2c83dd2b4a --- /dev/null +++ b/test/functional/fixtures/autoload/provider/brokencall.vim @@ -0,0 +1,2 @@ +" A dummy test provider +let g:provider#brokencall#enabled = 1 diff --git a/test/functional/fixtures/autoload/provider/brokenenabled.vim b/test/functional/fixtures/autoload/provider/brokenenabled.vim new file mode 100644 index 0000000000..54ed11cedc --- /dev/null +++ b/test/functional/fixtures/autoload/provider/brokenenabled.vim @@ -0,0 +1,6 @@ +" Dummy test provider, missing +" let g:provider#brokenenabled#enabled = 0 + +function! provider#brokenenabled#Call(method, args) + return 42 +endfunction diff --git a/test/functional/fixtures/autoload/provider/clipboard.vim b/test/functional/fixtures/autoload/provider/clipboard.vim index 6d777255c8..d1ddd81b12 100644 --- a/test/functional/fixtures/autoload/provider/clipboard.vim +++ b/test/functional/fixtures/autoload/provider/clipboard.vim @@ -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) diff --git a/test/functional/provider/provider_spec.lua b/test/functional/provider/provider_spec.lua new file mode 100644 index 0000000000..6f414f36f4 --- /dev/null +++ b/test/functional/provider/provider_spec.lua @@ -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) From 66938b928c05b913f3a11e520d13ca854621799d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 4 Aug 2019 03:54:06 +0200 Subject: [PATCH 2/6] provider: decide status by g:loaded_xx_provider --- runtime/autoload/provider/clipboard.vim | 22 +++++---- runtime/autoload/provider/node.vim | 9 ++-- runtime/autoload/provider/python.vim | 4 +- runtime/autoload/provider/python3.vim | 4 +- runtime/autoload/provider/ruby.vim | 8 ++-- runtime/doc/develop.txt | 46 +++++++++---------- src/nvim/eval.c | 36 +++++++++------ .../fixtures/autoload/provider/brokencall.vim | 2 +- .../autoload/provider/brokenenabled.vim | 4 +- .../fixtures/autoload/provider/clipboard.vim | 4 +- 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(':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 ":" 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) From 241956720d02d933b0b27097a3b0a1966f138d0b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 4 Aug 2019 12:06:24 +0200 Subject: [PATCH 3/6] provider: g:loaded_xx_provider=2 means "enabled and working" Value of 1 cannot be used, because users might set that in their vimrc to _disable_ a provider, which would confuse :checkhealth and has(). --- runtime/autoload/provider/clipboard.vim | 6 +++--- runtime/autoload/provider/node.vim | 6 +++--- runtime/autoload/provider/python.vim | 2 +- runtime/autoload/provider/python3.vim | 2 +- runtime/autoload/provider/ruby.vim | 6 +++--- runtime/doc/develop.txt | 6 +++--- runtime/doc/provider.txt | 8 ++++---- src/nvim/eval.c | 4 +++- test/functional/fixtures/autoload/provider/brokencall.vim | 2 +- test/functional/fixtures/autoload/provider/clipboard.vim | 2 +- 10 files changed, 23 insertions(+), 21 deletions(-) diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 9b79e5abec..ce140b0948 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -5,11 +5,11 @@ if exists('g:loaded_clipboard_provider') finish endif -" Default to FALSE. Set by provider#clipboard#Executable() later. +" Default to 1. provider#clipboard#Executable() may set 2. " To force a reload: " :unlet g:loaded_clipboard_provider " :runtime autoload/provider/clipboard.vim -let g:loaded_clipboard_provider = 0 +let g:loaded_clipboard_provider = 1 let s:copy = {} let s:paste = {} @@ -197,4 +197,4 @@ function! provider#clipboard#Call(method, args) abort endfunction " eval_has_provider() decides based on this variable. -let g:loaded_clipboard_provider = !empty(provider#clipboard#Executable()) +let g:loaded_clipboard_provider = empty(provider#clipboard#Executable()) ? 1 : 2 diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index dd2423fe2a..b2a3b3ee08 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -1,7 +1,7 @@ if exists('g:loaded_node_provider') finish endif -let g:loaded_node_provider = 0 +let g:loaded_node_provider = 1 function! s:is_minimum_version(version, min_major, min_minor) abort if empty(a:version) @@ -140,9 +140,9 @@ endfunction let s:err = '' let s:prog = provider#node#Detect() -let g:loaded_node_provider = !empty(s:prog) +let g:loaded_node_provider = empty(s:prog) ? 1 : 2 -if !g:loaded_node_provider +if g:loaded_node_provider != 2 let s:err = 'Cannot find the "neovim" node package. Try :checkhealth' endif diff --git a/runtime/autoload/provider/python.vim b/runtime/autoload/provider/python.vim index 7921701141..8a1d162784 100644 --- a/runtime/autoload/provider/python.vim +++ b/runtime/autoload/provider/python.vim @@ -8,7 +8,7 @@ if exists('g:loaded_python_provider') finish endif let [s:prog, s:err] = provider#pythonx#Detect(2) -let g:loaded_python_provider = !empty(s:prog) +let g:loaded_python_provider = empty(s:prog) ? 1 : 2 function! provider#python#Prog() abort return s:prog diff --git a/runtime/autoload/provider/python3.vim b/runtime/autoload/provider/python3.vim index 67350e3753..38ef0cccfc 100644 --- a/runtime/autoload/provider/python3.vim +++ b/runtime/autoload/provider/python3.vim @@ -8,7 +8,7 @@ if exists('g:loaded_python3_provider') finish endif let [s:prog, s:err] = provider#pythonx#Detect(3) -let g:loaded_python3_provider = !empty(s:prog) +let g:loaded_python3_provider = empty(s:prog) ? 1 : 2 function! provider#python3#Prog() abort return s:prog diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim index f9d4f2b885..f843050df9 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -2,7 +2,7 @@ if exists('g:loaded_ruby_provider') finish endif -let g:loaded_ruby_provider = 0 +let g:loaded_ruby_provider = 1 function! provider#ruby#Detect() abort return s:prog @@ -62,9 +62,9 @@ endfunction let s:err = '' let s:prog = s:detect() let s:plugin_path = expand(':p:h') . '/script_host.rb' -let g:loaded_ruby_provider = !empty(s:prog) +let g:loaded_ruby_provider = empty(s:prog) ? 1 : 2 -if !g:loaded_ruby_provider +if g:loaded_ruby_provider != 2 let s:err = 'Cannot find the neovim RubyGem. Try :checkhealth' endif diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 180612cf20..3262d9dec7 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -106,12 +106,12 @@ in eval.c: - eval_call_provider(name, method, arguments): calls provider#{name}#Call with the method and arguments. - 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. + which must be set to 2 by the provider script to indicate that 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, which sets `g:loaded_python_provider` -to TRUE only if a valid external Python host is found. Then `has("python")` +to 2 only if a valid external Python host is found. Then `has("python")` reflects whether Python support is working. *provider-reload* diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index 594c9602f4..dc045c360a 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -68,11 +68,11 @@ startup faster. Useful for working with virtualenvs. > < *g:loaded_python_provider* To disable Python 2 support: > - let g:loaded_python_provider = 1 + let g:loaded_python_provider = 0 < *g:loaded_python3_provider* To disable Python 3 support: > - let g:loaded_python3_provider = 1 + let g:loaded_python3_provider = 0 PYTHON VIRTUALENVS ~ @@ -111,7 +111,7 @@ Run |:checkhealth| to see if your system is up-to-date. RUBY PROVIDER CONFIGURATION ~ *g:loaded_ruby_provider* To disable Ruby support: > - let g:loaded_ruby_provider = 1 + let g:loaded_ruby_provider = 0 < *g:ruby_host_prog* Command to start the Ruby host. By default this is "neovim-ruby-host". With @@ -142,7 +142,7 @@ Run |:checkhealth| to see if your system is up-to-date. NODEJS PROVIDER CONFIGURATION~ *g:loaded_node_provider* To disable Node.js support: > - :let g:loaded_node_provider = 1 + :let g:loaded_node_provider = 0 < *g:node_host_prog* Command to start the Node.js host. Setting this makes startup faster. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 6b7a359508..3bcec56b06 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -23996,7 +23996,9 @@ bool eval_has_provider(const char *provider) } } - return (tv.v_type == VAR_NUMBER) ? !!tv.vval.v_number : false; + return (tv.v_type == VAR_NUMBER) + ? 2 == tv.vval.v_number // Value of 2 means "loaded and working". + : false; } /// Writes ":" to `buf[bufsize]`. diff --git a/test/functional/fixtures/autoload/provider/brokencall.vim b/test/functional/fixtures/autoload/provider/brokencall.vim index 7dc5d828d2..cad2b2a640 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:loaded_brokencall_provider = 1 +let g:loaded_brokencall_provider = 2 diff --git a/test/functional/fixtures/autoload/provider/clipboard.vim b/test/functional/fixtures/autoload/provider/clipboard.vim index efa9f82bd4..41e486b745 100644 --- a/test/functional/fixtures/autoload/provider/clipboard.vim +++ b/test/functional/fixtures/autoload/provider/clipboard.vim @@ -1,4 +1,4 @@ -let g:loaded_clipboard_provider = 1 +let g:loaded_clipboard_provider = 2 let g:test_clip = { '+': [''], '*': [''], } From 5e6a08f2e6b21e83c9fb381042f0aed89de4598d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 4 Aug 2019 12:20:30 +0200 Subject: [PATCH 4/6] provider: skip non-provider has() feature-names We don't want to retry autoload sourcing (slow) for every random has() query that finds it way to eval_call_provider(). --- src/nvim/eval.c | 21 +++++++++++++------ .../fixtures/autoload/provider/brokencall.vim | 2 -- .../{brokenenabled.vim => python.vim} | 2 +- .../fixtures/autoload/provider/ruby.vim | 2 ++ test/functional/provider/provider_spec.lua | 10 ++++++--- 5 files changed, 25 insertions(+), 12 deletions(-) delete mode 100644 test/functional/fixtures/autoload/provider/brokencall.vim rename test/functional/fixtures/autoload/provider/{brokenenabled.vim => python.vim} (70%) create mode 100644 test/functional/fixtures/autoload/provider/ruby.vim diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 3bcec56b06..dc18532b40 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -23969,28 +23969,37 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) } /// Checks if a named provider is enabled. -bool eval_has_provider(const char *provider) +bool eval_has_provider(const char *name) { + if (!strequal(name, "clipboard") + && !strequal(name, "python") + && !strequal(name, "python3") + && !strequal(name, "ruby") + && !strequal(name, "node")) { + // Avoid autoload for non-provider has() features. + return false; + } + char buf[256]; int len; typval_T tv; // Get the g:loaded_xx_provider variable. - len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", provider); + len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name); if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) { // Trigger autoload once. - len = snprintf(buf, sizeof(buf), "provider#%s#bogus", provider); + len = snprintf(buf, sizeof(buf), "provider#%s#bogus", name); script_autoload(buf, len, false); // Retry the (non-autoload-style) variable. - len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", provider); + len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name); 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); + snprintf(buf, sizeof(buf), "provider#%s#Call", name); 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); + name, name); } return false; } diff --git a/test/functional/fixtures/autoload/provider/brokencall.vim b/test/functional/fixtures/autoload/provider/brokencall.vim deleted file mode 100644 index cad2b2a640..0000000000 --- a/test/functional/fixtures/autoload/provider/brokencall.vim +++ /dev/null @@ -1,2 +0,0 @@ -" A dummy test provider -let g:loaded_brokencall_provider = 2 diff --git a/test/functional/fixtures/autoload/provider/brokenenabled.vim b/test/functional/fixtures/autoload/provider/python.vim similarity index 70% rename from test/functional/fixtures/autoload/provider/brokenenabled.vim rename to test/functional/fixtures/autoload/provider/python.vim index dd33ce66f3..d68360ac30 100644 --- a/test/functional/fixtures/autoload/provider/brokenenabled.vim +++ b/test/functional/fixtures/autoload/provider/python.vim @@ -1,6 +1,6 @@ " Dummy test provider, missing this required variable: " let g:loaded_brokenenabled_provider = 0 -function! provider#brokenenabled#Call(method, args) +function! provider#python#Call(method, args) return 42 endfunction diff --git a/test/functional/fixtures/autoload/provider/ruby.vim b/test/functional/fixtures/autoload/provider/ruby.vim new file mode 100644 index 0000000000..35becc27ff --- /dev/null +++ b/test/functional/fixtures/autoload/provider/ruby.vim @@ -0,0 +1,2 @@ +" A dummy test provider +let g:loaded_ruby_provider = 2 diff --git a/test/functional/provider/provider_spec.lua b/test/functional/provider/provider_spec.lua index b690cf2566..4220ec21cf 100644 --- a/test/functional/provider/provider_spec.lua +++ b/test/functional/provider/provider_spec.lua @@ -11,11 +11,15 @@ describe('providers', function() 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')") + -- Using test-fixture with broken impl: + -- test/functional/fixtures/autoload/provider/python.vim + expect_err('Vim:provider: python: missing required variable g:loaded_python_provider', + eval, "has('python')") end) it('without Call() but with g:loaded_xx_provider', function() - eq(1, eval("has('brokencall')")) + -- Using test-fixture with broken impl: + -- test/functional/fixtures/autoload/provider/ruby.vim + eq(1, eval("has('ruby')")) end) end) From e952b7fc2f9838441308d1f71b98f108ae5faa8a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 4 Aug 2019 12:27:44 +0200 Subject: [PATCH 5/6] health.vim: check has("debug") --- runtime/autoload/health/nvim.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index a2227e3b29..c25f5ee64f 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -123,7 +123,7 @@ function! s:check_performance() abort else call health#report_info(buildtype) call health#report_warn( - \ 'Non-optimized build-type. Nvim will be slower.', + \ 'Non-optimized '.(has('debug')?'(DEBUG) ':'').'build. Nvim will be slower.', \ ['Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.', \ s:suggest_faq]) endif From 2141dc22625f73f3ce73460e581934b94f141cf9 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 4 Aug 2019 12:37:05 +0200 Subject: [PATCH 6/6] provider: check #Call() if g:loaded_xx_provider=2 --- src/nvim/eval.c | 17 ++++++++++++++--- test/functional/provider/provider_spec.lua | 9 +++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index dc18532b40..d82a081c27 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -23996,8 +23996,7 @@ bool eval_has_provider(const char *name) 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", name); - bool has_call = !!find_func((char_u *)buf); - if (has_call && p_lpl) { + if (!!find_func((char_u *)buf) && p_lpl) { emsgf("provider: %s: missing required variable g:loaded_%s_provider", name, name); } @@ -24005,9 +24004,21 @@ bool eval_has_provider(const char *name) } } - return (tv.v_type == VAR_NUMBER) + bool ok = (tv.v_type == VAR_NUMBER) ? 2 == tv.vval.v_number // Value of 2 means "loaded and working". : false; + + if (ok) { + // Call() must be defined if provider claims to be working. + snprintf(buf, sizeof(buf), "provider#%s#Call", name); + if (!find_func((char_u *)buf)) { + emsgf("provider: %s: g:loaded_%s_provider=2 but %s is not defined", + name, name, buf); + ok = false; + } + } + + return ok; } /// Writes ":" to `buf[bufsize]`. diff --git a/test/functional/provider/provider_spec.lua b/test/functional/provider/provider_spec.lua index 4220ec21cf..bfb0bbc3a3 100644 --- a/test/functional/provider/provider_spec.lua +++ b/test/functional/provider/provider_spec.lua @@ -1,6 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) -local clear, eq, eval = helpers.clear, helpers.eq, helpers.eval +local clear, eval = helpers.clear, helpers.eval local command = helpers.command local expect_err = helpers.expect_err @@ -9,7 +9,7 @@ describe('providers', function() clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp') end) - it('must define g:loaded_xx_provider', function() + it('with #Call(), missing g:loaded_xx_provider', function() command('set loadplugins') -- Using test-fixture with broken impl: -- test/functional/fixtures/autoload/provider/python.vim @@ -17,9 +17,10 @@ describe('providers', function() eval, "has('python')") end) - it('without Call() but with g:loaded_xx_provider', function() + it('with g:loaded_xx_provider, missing #Call()', function() -- Using test-fixture with broken impl: -- test/functional/fixtures/autoload/provider/ruby.vim - eq(1, eval("has('ruby')")) + expect_err('Vim:provider: ruby: g:loaded_ruby_provider=2 but provider#ruby#Call is not defined', + eval, "has('ruby')") end) end)