aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/provider/clipboard.vim22
-rw-r--r--runtime/autoload/provider/node.vim9
-rw-r--r--runtime/autoload/provider/python.vim4
-rw-r--r--runtime/autoload/provider/python3.vim4
-rw-r--r--runtime/autoload/provider/ruby.vim8
-rw-r--r--runtime/doc/develop.txt46
-rw-r--r--src/nvim/eval.c36
-rw-r--r--test/functional/fixtures/autoload/provider/brokencall.vim2
-rw-r--r--test/functional/fixtures/autoload/provider/brokenenabled.vim4
-rw-r--r--test/functional/fixtures/autoload/provider/clipboard.vim4
-rw-r--r--test/functional/provider/provider_spec.lua16
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)