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
commit 96be8a2c4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 124 additions and 90 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"); \
} \
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;
}
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;
char buf[256];
int len;
typval_T tv;
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);
// 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]`.

View File

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

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)