mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	runtime: Reimplement python/clipboard providers in vimscript
Clipboard is implemented with platform-specific shell commands, and python is implemented with the external plugin facility (rpc#* functions). The script_host.py file(legacy python-vim emulation plugin) was moved/adapted from the python client repository.
This commit is contained in:
		
							
								
								
									
										40
									
								
								runtime/autoload/provider/clipboard.vim
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								runtime/autoload/provider/clipboard.vim
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| " The clipboard provider uses shell commands to communicate with the clipboard. | ||||
| " The provider function will only be registered if one of the supported | ||||
| " commands are available. | ||||
| let s:copy = '' | ||||
| let s:paste = '' | ||||
|  | ||||
| if executable('pbcopy') | ||||
|   let s:copy = 'pbcopy' | ||||
|   let s:paste = 'pbpaste' | ||||
| elseif executable('xsel') | ||||
|   let s:copy = 'xsel -i -b' | ||||
|   let s:paste = 'xsel -o -b' | ||||
| elseif executable('xclip') | ||||
|   let s:copy = 'xclip -i -selection clipboard' | ||||
|   let s:paste = 'xclip -o -selection clipboard' | ||||
| endif | ||||
|  | ||||
| if s:copy == '' | ||||
|   echom 'No shell command for communicating with the clipboard found.' | ||||
|   finish | ||||
| endif | ||||
|  | ||||
| let s:methods = {} | ||||
|  | ||||
| function! s:ClipboardGet(...) | ||||
|   return systemlist(s:paste) | ||||
| endfunction | ||||
|  | ||||
| function! s:ClipboardSet(...) | ||||
|   call systemlist(s:copy, a:1) | ||||
| endfunction | ||||
|  | ||||
| let s:methods = { | ||||
|       \ 'get': function('s:ClipboardGet'), | ||||
|       \ 'set': function('s:ClipboardSet') | ||||
|       \ } | ||||
|  | ||||
| function! provider#clipboard#Call(method, args) | ||||
|   return s:methods[a:method](a:args) | ||||
| endfunction | ||||
							
								
								
									
										28
									
								
								runtime/autoload/provider/python.vim
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								runtime/autoload/provider/python.vim
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| " The python provider uses a python host to emulate an environment for running | ||||
| " python-vim plugins(:h python-vim). See :h nvim-providers for more | ||||
| " information. | ||||
| " | ||||
| " Associating the plugin with the python host is the first step because plugins | ||||
| " will be passed as command-line arguments | ||||
| if exists('s:loaded_python_provider') || &cp | ||||
|   finish | ||||
| endif | ||||
| let s:loaded_python_provider = 1 | ||||
| let s:plugin_path = expand('<sfile>:p:h').'/script_host.py' | ||||
| " The python provider plugin will run in a separate instance of the python | ||||
| " host. | ||||
| call rpc#host#RegisterClone('legacy-python-provider', 'python') | ||||
| call rpc#host#RegisterPlugin('legacy-python-provider', s:plugin_path, []) | ||||
| " Ensure that we can load the python host before bootstrapping | ||||
| try | ||||
|   let s:host = rpc#host#Require('legacy-python-provider') | ||||
| catch | ||||
|   echomsg v:exception | ||||
|   finish | ||||
| endtry | ||||
|  | ||||
| let s:rpcrequest = function('rpcrequest') | ||||
|  | ||||
| function! provider#python#Call(method, args) | ||||
|   return call(s:rpcrequest, insert(insert(a:args, 'python_'.a:method), s:host)) | ||||
| endfunction | ||||
							
								
								
									
										243
									
								
								runtime/autoload/provider/script_host.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								runtime/autoload/provider/script_host.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,243 @@ | ||||
| """Legacy python-vim emulation.""" | ||||
| import imp | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
|  | ||||
| import neovim | ||||
|  | ||||
| __all__ = ('ScriptHost',) | ||||
|  | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
| debug, info, warn = (logger.debug, logger.info, logger.warn,) | ||||
|  | ||||
| IS_PYTHON3 = sys.version_info >= (3, 0) | ||||
|  | ||||
| if IS_PYTHON3: | ||||
|     basestring = str | ||||
|  | ||||
|  | ||||
| @neovim.plugin | ||||
| class ScriptHost(object): | ||||
|  | ||||
|     """Provides an environment for running python plugins created for Vim.""" | ||||
|  | ||||
|     def __init__(self, nvim): | ||||
|         """Initialize the legacy python-vim environment.""" | ||||
|         self.setup(nvim) | ||||
|         # context where all code will run | ||||
|         self.module = imp.new_module('__main__') | ||||
|         nvim.script_context = self.module | ||||
|         # it seems some plugins assume 'sys' is already imported, so do it now | ||||
|         exec('import sys', self.module.__dict__) | ||||
|         self.legacy_vim = nvim.with_hook(LegacyEvalHook()) | ||||
|         if IS_PYTHON3: | ||||
|             self.legacy_vim = self.legacy_vim.with_hook( | ||||
|                 neovim.DecodeHook( | ||||
|                     encoding=nvim.options['encoding'].decode('ascii'))) | ||||
|         sys.modules['vim'] = self.legacy_vim | ||||
|  | ||||
|     def setup(self, nvim): | ||||
|         """Setup import hooks and global streams. | ||||
|  | ||||
|         This will add import hooks for importing modules from runtime | ||||
|         directories and patch the sys module so 'print' calls will be | ||||
|         forwarded to Nvim. | ||||
|         """ | ||||
|         self.nvim = nvim | ||||
|         info('install import hook/path') | ||||
|         self.hook = path_hook(nvim) | ||||
|         sys.path_hooks.append(self.hook) | ||||
|         nvim.VIM_SPECIAL_PATH = '_vim_path_' | ||||
|         sys.path.append(nvim.VIM_SPECIAL_PATH) | ||||
|         info('redirect sys.stdout and sys.stderr') | ||||
|         self.saved_stdout = sys.stdout | ||||
|         self.saved_stderr = sys.stderr | ||||
|         sys.stdout = RedirectStream(lambda data: nvim.out_write(data)) | ||||
|         sys.stderr = RedirectStream(lambda data: nvim.err_write(data)) | ||||
|  | ||||
|     def teardown(self): | ||||
|         """Restore state modified from the `setup` call.""" | ||||
|         for plugin in self.installed_plugins: | ||||
|             if hasattr(plugin, 'on_teardown'): | ||||
|                 plugin.teardown() | ||||
|         nvim = self.nvim | ||||
|         info('uninstall import hook/path') | ||||
|         sys.path.remove(nvim.VIM_SPECIAL_PATH) | ||||
|         sys.path_hooks.remove(self.hook) | ||||
|         info('restore sys.stdout and sys.stderr') | ||||
|         sys.stdout = self.saved_stdout | ||||
|         sys.stderr = self.saved_stderr | ||||
|  | ||||
|     @neovim.rpc_export('python_execute', sync=True) | ||||
|     def python_execute(self, script, range_start, range_stop): | ||||
|         """Handle the `python` ex command.""" | ||||
|         self._set_current_range(range_start, range_stop) | ||||
|         exec(script, self.module.__dict__) | ||||
|  | ||||
|     @neovim.rpc_export('python_execute_file', sync=True) | ||||
|     def python_execute_file(self, file_path, range_start, range_stop): | ||||
|         """Handle the `pyfile` ex command.""" | ||||
|         self._set_current_range(range_start, range_stop) | ||||
|         with open(file_path) as f: | ||||
|             script = compile(f.read(), file_path, 'exec') | ||||
|             exec(script, self.module.__dict__) | ||||
|  | ||||
|     @neovim.rpc_export('python_do_range', sync=True) | ||||
|     def python_do_range(self, start, stop, code): | ||||
|         """Handle the `pydo` ex command.""" | ||||
|         self._set_current_range(start, stop) | ||||
|         nvim = self.nvim | ||||
|         start -= 1 | ||||
|         stop -= 1 | ||||
|         fname = '_vim_pydo' | ||||
|  | ||||
|         # Python3 code (exec) must be a string, mixing bytes with | ||||
|         # function_def would use bytes.__repr__ instead | ||||
|         if isinstance and isinstance(code, bytes): | ||||
|             code = code.decode(nvim.options['encoding'].decode('ascii')) | ||||
|         # define the function | ||||
|         function_def = 'def %s(line, linenr):\n %s' % (fname, code,) | ||||
|         exec(function_def, self.module.__dict__) | ||||
|         # get the function | ||||
|         function = self.module.__dict__[fname] | ||||
|         while start <= stop: | ||||
|             # Process batches of 5000 to avoid the overhead of making multiple | ||||
|             # API calls for every line. Assuming an average line length of 100 | ||||
|             # bytes, approximately 488 kilobytes will be transferred per batch, | ||||
|             # which can be done very quickly in a single API call. | ||||
|             sstart = start | ||||
|             sstop = min(start + 5000, stop) | ||||
|             lines = nvim.current.buffer.get_line_slice(sstart, sstop, True, | ||||
|                                                        True) | ||||
|  | ||||
|             exception = None | ||||
|             newlines = [] | ||||
|             linenr = sstart + 1 | ||||
|             for i, line in enumerate(lines): | ||||
|                 result = function(line, linenr) | ||||
|                 if result is None: | ||||
|                     # Update earlier lines, and skip to the next | ||||
|                     if newlines: | ||||
|                         end = sstart + len(newlines) - 1 | ||||
|                         nvim.current.buffer.set_line_slice(sstart, end, | ||||
|                                                            True, True, | ||||
|                                                            newlines) | ||||
|                     sstart += len(newlines) + 1 | ||||
|                     newlines = [] | ||||
|                     pass | ||||
|                 elif isinstance(result, basestring): | ||||
|                     newlines.append(result) | ||||
|                 else: | ||||
|                     exception = TypeError('pydo should return a string ' + | ||||
|                                           'or None, found %s instead' | ||||
|                                           % result.__class__.__name__) | ||||
|                     break | ||||
|                 linenr += 1 | ||||
|  | ||||
|             start = sstop + 1 | ||||
|             if newlines: | ||||
|                 end = sstart + len(newlines) - 1 | ||||
|                 nvim.current.buffer.set_line_slice(sstart, end, True, True, | ||||
|                                                    newlines) | ||||
|             if exception: | ||||
|                 raise exception | ||||
|         # delete the function | ||||
|         del self.module.__dict__[fname] | ||||
|  | ||||
|     @neovim.rpc_export('python_eval', sync=True) | ||||
|     def python_eval(self, expr): | ||||
|         """Handle the `pyeval` vim function.""" | ||||
|         return eval(expr, self.module.__dict__) | ||||
|  | ||||
|     def _set_current_range(self, start, stop): | ||||
|         current = self.legacy_vim.current | ||||
|         current.range = current.buffer.range(start, stop) | ||||
|  | ||||
|  | ||||
| class RedirectStream(object): | ||||
|     def __init__(self, redirect_handler): | ||||
|         self.redirect_handler = redirect_handler | ||||
|  | ||||
|     def write(self, data): | ||||
|         self.redirect_handler(data) | ||||
|  | ||||
|     def writelines(self, seq): | ||||
|         self.redirect_handler('\n'.join(seq)) | ||||
|  | ||||
|  | ||||
| class LegacyEvalHook(neovim.SessionHook): | ||||
|  | ||||
|     """Injects legacy `vim.eval` behavior to a Nvim instance.""" | ||||
|  | ||||
|     def __init__(self): | ||||
|         super(LegacyEvalHook, self).__init__(from_nvim=self._string_eval) | ||||
|  | ||||
|     def _string_eval(self, obj, session, method, kind): | ||||
|         if method == 'vim_eval' and isinstance(obj, (int, long, float)): | ||||
|             return str(obj) | ||||
|         return obj | ||||
|  | ||||
|  | ||||
| # This was copied/adapted from nvim-python help | ||||
| def path_hook(nvim): | ||||
|     def _get_paths(): | ||||
|         return discover_runtime_directories(nvim) | ||||
|  | ||||
|     def _find_module(fullname, oldtail, path): | ||||
|         idx = oldtail.find('.') | ||||
|         if idx > 0: | ||||
|             name = oldtail[:idx] | ||||
|             tail = oldtail[idx+1:] | ||||
|             fmr = imp.find_module(name, path) | ||||
|             module = imp.load_module(fullname[:-len(oldtail)] + name, *fmr) | ||||
|             return _find_module(fullname, tail, module.__path__) | ||||
|         else: | ||||
|             fmr = imp.find_module(fullname, path) | ||||
|             return imp.load_module(fullname, *fmr) | ||||
|  | ||||
|     class VimModuleLoader(object): | ||||
|         def __init__(self, module): | ||||
|             self.module = module | ||||
|  | ||||
|         def load_module(self, fullname, path=None): | ||||
|             return self.module | ||||
|  | ||||
|     class VimPathFinder(object): | ||||
|         @classmethod | ||||
|         def find_module(cls, fullname, path=None): | ||||
|             try: | ||||
|                 return VimModuleLoader( | ||||
|                     _find_module(fullname, fullname, path or _get_paths())) | ||||
|             except ImportError: | ||||
|                 return None | ||||
|  | ||||
|         @classmethod | ||||
|         def load_module(cls, fullname, path=None): | ||||
|             return _find_module(fullname, fullname, path or _get_paths()) | ||||
|  | ||||
|     def hook(path): | ||||
|         if path == nvim.VIM_SPECIAL_PATH: | ||||
|             return VimPathFinder | ||||
|         else: | ||||
|             raise ImportError | ||||
|  | ||||
|     return hook | ||||
|  | ||||
|  | ||||
| def discover_runtime_directories(nvim): | ||||
|     rv = [] | ||||
|     for path in nvim.list_runtime_paths(): | ||||
|         if not os.path.exists(path): | ||||
|             continue | ||||
|         path1 = os.path.join(path, b'pythonx') | ||||
|         if IS_PYTHON3: | ||||
|             path2 = os.path.join(path, b'python3') | ||||
|         else: | ||||
|             path2 = os.path.join(path, b'python2') | ||||
|         if os.path.exists(path1): | ||||
|             rv.append(path1) | ||||
|         if os.path.exists(path2): | ||||
|             rv.append(path2) | ||||
|     return rv | ||||
		Reference in New Issue
	
	Block a user
	 Thiago de Arruda
					Thiago de Arruda