mirror of
https://github.com/neovim/neovim.git
synced 2025-09-07 11:58:17 +00:00
Merge #10161 from equalsraf/tb-clipboard-reload
Support "reload" of providers
This commit is contained in:
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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')
|
||||||
|
@@ -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')
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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*
|
||||||
|
|
||||||
|
@@ -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.
|
||||||
|
@@ -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]`.
|
||||||
|
@@ -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
|
||||||
|
6
test/functional/fixtures/autoload/provider/python.vim
Normal file
6
test/functional/fixtures/autoload/provider/python.vim
Normal 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
|
2
test/functional/fixtures/autoload/provider/ruby.vim
Normal file
2
test/functional/fixtures/autoload/provider/ruby.vim
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
" A dummy test provider
|
||||||
|
let g:loaded_ruby_provider = 2
|
26
test/functional/provider/provider_spec.lua
Normal file
26
test/functional/provider/provider_spec.lua
Normal 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)
|
Reference in New Issue
Block a user