Merge #10161 from equalsraf/tb-clipboard-reload

Support "reload" of providers
This commit is contained in:
Justin M. Keyes
2019-08-04 15:52:56 +02:00
committed by GitHub
13 changed files with 124 additions and 90 deletions

View File

@@ -123,7 +123,7 @@ function! s:check_performance() abort
else else
call health#report_info(buildtype) call health#report_info(buildtype)
call health#report_warn( 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`.', \ ['Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.',
\ s:suggest_faq]) \ s:suggest_faq])
endif endif

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 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:copy = {}
let s:paste = {} let s:paste = {}
let s:clipboard = {} let s:clipboard = {}
@@ -120,13 +130,6 @@ function! provider#clipboard#Executable() abort
return '' return ''
endfunction 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 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]()
@@ -192,3 +195,6 @@ function! provider#clipboard#Call(method, args) abort
let s:here = v:false let s:here = v:false
endtry endtry
endfunction endfunction
" eval_has_provider() decides based on this variable.
let g:loaded_clipboard_provider = empty(provider#clipboard#Executable()) ? 1 : 2

View File

@@ -140,8 +140,9 @@ 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) ? 1 : 2
if empty(s:prog) if g:loaded_node_provider != 2
let s:err = 'Cannot find the "neovim" node package. Try :checkhealth' let s:err = 'Cannot find the "neovim" node package. Try :checkhealth'
endif endif

View File

@@ -7,9 +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:loaded_python_provider = empty(s:prog) ? 1 : 2
function! provider#python#Prog() abort function! provider#python#Prog() abort
return s:prog return s:prog
@@ -19,11 +18,6 @@ function! provider#python#Error() abort
return s:err return s:err
endfunction endfunction
if s:prog == ''
" Detection failed
finish
endif
" The Python provider plugin will run in a separate instance of the Python " The Python provider plugin will run in a separate instance of the Python
" host. " host.
call remote#host#RegisterClone('legacy-python-provider', 'python') call remote#host#RegisterClone('legacy-python-provider', 'python')

View File

@@ -7,9 +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:loaded_python3_provider = empty(s:prog) ? 1 : 2
function! provider#python3#Prog() abort function! provider#python3#Prog() abort
return s:prog return s:prog
@@ -19,11 +18,6 @@ function! provider#python3#Error() abort
return s:err return s:err
endfunction endfunction
if s:prog == ''
" Detection failed
finish
endif
" The Python3 provider plugin will run in a separate instance of the Python3 " The Python3 provider plugin will run in a separate instance of the Python3
" host. " host.
call remote#host#RegisterClone('legacy-python3-provider', 'python3') call remote#host#RegisterClone('legacy-python3-provider', 'python3')

View File

@@ -62,8 +62,9 @@ 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) ? 1 : 2
if empty(s:prog) if g:loaded_ruby_provider != 2
let s:err = 'Cannot find the neovim RubyGem. Try :checkhealth' let s:err = 'Cannot find the neovim RubyGem. Try :checkhealth'
endif endif

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)#Call function is implemented. Called by |has()| which must be set to 2 by the provider script to indicate that it is
(vimscript) to check if features are available. "enabled 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 provider#python#Call function is only "autoload/provider/python.vim" script, which sets `g:loaded_python_provider`
defined if a valid external Python host is found. That works well with the to 2 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

@@ -68,11 +68,11 @@ startup faster. Useful for working with virtualenvs. >
< <
*g:loaded_python_provider* *g:loaded_python_provider*
To disable Python 2 support: > To disable Python 2 support: >
let g:loaded_python_provider = 1 let g:loaded_python_provider = 0
< <
*g:loaded_python3_provider* *g:loaded_python3_provider*
To disable Python 3 support: > To disable Python 3 support: >
let g:loaded_python3_provider = 1 let g:loaded_python3_provider = 0
PYTHON VIRTUALENVS ~ PYTHON VIRTUALENVS ~
@@ -111,7 +111,7 @@ Run |:checkhealth| to see if your system is up-to-date.
RUBY PROVIDER CONFIGURATION ~ RUBY PROVIDER CONFIGURATION ~
*g:loaded_ruby_provider* *g:loaded_ruby_provider*
To disable Ruby support: > To disable Ruby support: >
let g:loaded_ruby_provider = 1 let g:loaded_ruby_provider = 0
< <
*g:ruby_host_prog* *g:ruby_host_prog*
Command to start the Ruby host. By default this is "neovim-ruby-host". With 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~ NODEJS PROVIDER CONFIGURATION~
*g:loaded_node_provider* *g:loaded_node_provider*
To disable Node.js support: > To disable Node.js support: >
:let g:loaded_node_provider = 1 :let g:loaded_node_provider = 0
< <
*g:node_host_prog* *g:node_host_prog*
Command to start the Node.js host. Setting this makes startup faster. Command to start the Node.js host. Setting this makes startup faster.

View File

@@ -23968,52 +23968,57 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
return rettv; return rettv;
} }
/// Checks if a named provider is enabled.
bool eval_has_provider(const char *name) bool eval_has_provider(const char *name)
{ {
#define CHECK_PROVIDER(name) \ if (!strequal(name, "clipboard")
if (has_##name == -1) { \ && !strequal(name, "python")
has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ && !strequal(name, "python3")
if (!has_##name) { \ && !strequal(name, "ruby")
script_autoload("provider#" #name "#Call", \ && !strequal(name, "node")) {
sizeof("provider#" #name "#Call") - 1, \ // Avoid autoload for non-provider has() features.
false); \ return false;
has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \
} \
} }
static int has_clipboard = -1; char buf[256];
static int has_python = -1; int len;
static int has_python3 = -1; typval_T tv;
static int has_ruby = -1;
typval_T args[1];
args[0].v_type = VAR_UNKNOWN;
if (strequal(name, "clipboard")) { // Get the g:loaded_xx_provider variable.
CHECK_PROVIDER(clipboard); len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name);
return has_clipboard; if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) {
} else if (strequal(name, "python3")) { // Trigger autoload once.
CHECK_PROVIDER(python3); len = snprintf(buf, sizeof(buf), "provider#%s#bogus", name);
return has_python3; script_autoload(buf, len, false);
} else if (strequal(name, "python")) {
CHECK_PROVIDER(python); // Retry the (non-autoload-style) variable.
return has_python; len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name);
} else if (strequal(name, "ruby")) { if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) {
bool need_check_ruby = (has_ruby == -1); // Show a hint if Call() is defined but g:loaded_xx_provider is missing.
CHECK_PROVIDER(ruby); snprintf(buf, sizeof(buf), "provider#%s#Call", name);
if (need_check_ruby && has_ruby == 1) { if (!!find_func((char_u *)buf) && p_lpl) {
char *rubyhost = call_func_retstr("provider#ruby#Detect", 0, args, true); emsgf("provider: %s: missing required variable g:loaded_%s_provider",
if (rubyhost) { name, name);
if (*rubyhost == NUL) {
// Invalid rubyhost executable. Gem is probably not installed.
has_ruby = 0;
}
xfree(rubyhost);
} }
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]`. /// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`.

View File

@@ -1,3 +1,5 @@
let g:loaded_clipboard_provider = 2
let g:test_clip = { '+': [''], '*': [''], } let g:test_clip = { '+': [''], '*': [''], }
let s:methods = {} let s:methods = {}
@@ -35,7 +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
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

@@ -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

View File

@@ -0,0 +1,2 @@
" A dummy test provider
let g:loaded_ruby_provider = 2

View File

@@ -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)