mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +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 | ||||
|     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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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') | ||||
|   | ||||
| @@ -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') | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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* | ||||
|  | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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]`. | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										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
	 Justin M. Keyes
					Justin M. Keyes