diff options
-rw-r--r-- | runtime/autoload/health/nvim.vim | 2 | ||||
-rw-r--r-- | runtime/autoload/provider/clipboard.vim | 20 | ||||
-rw-r--r-- | runtime/autoload/provider/node.vim | 3 | ||||
-rw-r--r-- | runtime/autoload/provider/python.vim | 8 | ||||
-rw-r--r-- | runtime/autoload/provider/python3.vim | 8 | ||||
-rw-r--r-- | runtime/autoload/provider/ruby.vim | 3 | ||||
-rw-r--r-- | runtime/doc/develop.txt | 46 | ||||
-rw-r--r-- | runtime/doc/provider.txt | 8 | ||||
-rw-r--r-- | src/nvim/eval.c | 85 | ||||
-rw-r--r-- | test/functional/fixtures/autoload/provider/clipboard.vim | 3 | ||||
-rw-r--r-- | test/functional/fixtures/autoload/provider/python.vim | 6 | ||||
-rw-r--r-- | test/functional/fixtures/autoload/provider/ruby.vim | 2 | ||||
-rw-r--r-- | test/functional/provider/provider_spec.lua | 26 |
13 files changed, 127 insertions, 93 deletions
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 diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 2b06ee8c48..ce140b0948 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 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 = 1 + let s:copy = {} let s:paste = {} let s:clipboard = {} @@ -120,13 +130,6 @@ 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 - function! s:clipboard.get(reg) abort if type(s:paste[a:reg]) == v:t_func return s:paste[a:reg]() @@ -192,3 +195,6 @@ function! provider#clipboard#Call(method, args) abort let s:here = v:false endtry endfunction + +" eval_has_provider() decides based on this variable. +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 35882849bd..b2a3b3ee08 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -140,8 +140,9 @@ endfunction let s:err = '' let s:prog = provider#node#Detect() +let g:loaded_node_provider = empty(s:prog) ? 1 : 2 -if empty(s:prog) +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 a06cbe4814..8a1d162784 100644 --- a/runtime/autoload/provider/python.vim +++ b/runtime/autoload/provider/python.vim @@ -7,9 +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:loaded_python_provider = empty(s:prog) ? 1 : 2 function! provider#python#Prog() abort return s:prog @@ -19,11 +18,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..38ef0cccfc 100644 --- a/runtime/autoload/provider/python3.vim +++ b/runtime/autoload/provider/python3.vim @@ -7,9 +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:loaded_python3_provider = empty(s:prog) ? 1 : 2 function! provider#python3#Prog() abort return s:prog @@ -19,11 +18,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..f843050df9 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -62,8 +62,9 @@ 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) ? 1 : 2 -if empty(s:prog) +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 843e23ee54..3262d9dec7 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)#Call function is implemented. 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 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; the provider#python#Call function is only -defined 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 2 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/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 cefd351dd7..d82a081c27 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -23968,52 +23968,57 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) return rettv; } +/// Checks if a named provider is enabled. bool eval_has_provider(const char *name) { -#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"); \ - } \ - } - - 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; - - 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 (!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", name); + if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) { + // Trigger autoload once. + 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", 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); + if (!!find_func((char_u *)buf) && p_lpl) { + emsgf("provider: %s: missing required variable g:loaded_%s_provider", + name, name); } + return false; } - return has_ruby; } - return false; + 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 "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`. diff --git a/test/functional/fixtures/autoload/provider/clipboard.vim b/test/functional/fixtures/autoload/provider/clipboard.vim index 6d777255c8..41e486b745 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 = 2 + let g:test_clip = { '+': [''], '*': [''], } let s:methods = {} @@ -35,7 +37,6 @@ function! s:methods.set(lines, regtype, reg) let g:test_clip[a:reg] = [a:lines, a:regtype] endfunction - function! provider#clipboard#Call(method, args) return call(s:methods[a:method],a:args,s:methods) endfunction diff --git a/test/functional/fixtures/autoload/provider/python.vim b/test/functional/fixtures/autoload/provider/python.vim new file mode 100644 index 0000000000..d68360ac30 --- /dev/null +++ b/test/functional/fixtures/autoload/provider/python.vim @@ -0,0 +1,6 @@ +" Dummy test provider, missing this required variable: +" let g:loaded_brokenenabled_provider = 0 + +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 new file mode 100644 index 0000000000..bfb0bbc3a3 --- /dev/null +++ b/test/functional/provider/provider_spec.lua @@ -0,0 +1,26 @@ + +local helpers = require('test.functional.helpers')(after_each) +local clear, eval = helpers.clear, helpers.eval +local command = helpers.command +local expect_err = helpers.expect_err + +describe('providers', function() + before_each(function() + clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp') + end) + + 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 + expect_err('Vim:provider: python: missing required variable g:loaded_python_provider', + eval, "has('python')") + end) + + it('with g:loaded_xx_provider, missing #Call()', function() + -- Using test-fixture with broken impl: + -- test/functional/fixtures/autoload/provider/ruby.vim + expect_err('Vim:provider: ruby: g:loaded_ruby_provider=2 but provider#ruby#Call is not defined', + eval, "has('ruby')") + end) +end) |