Skip to content

Commit

Permalink
host: do not construct plugin objects on UpdateRemotePlugins
Browse files Browse the repository at this point in the history
Also if plugin A is invoked, don't construct plugin B until any of B:s
methods is invoked.
  • Loading branch information
bfredl committed Nov 16, 2019
1 parent 9923ee3 commit d3c389f
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 20 deletions.
15 changes: 9 additions & 6 deletions docs/usage/remote-plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,15 @@ but it can make asynchronous requests, i.e. passing ``async_=True``.

.. note::

Plugins must not invoke API methods in ``__init__`` or global module scope
(or really do anything with non-trivial side-effects). A well-behaved rplugin
will not start executing until its functionality is requested by the user.
Initialize the plugin the first time the user invokes a command, or use an
appropriate autocommand, if it e.g. makes sense to automatically start the
plugin for a given filetype.
Plugin objects are constructed the first time any request of the class is
invoked. Any error in ``__init__`` will be reported as an error from this
first request. A well-behaved rplugin will not start executing until its
functionality is requested by the user. Initialize the plugin when user
invokes a command, or use an appropriate autocommand, e.g. FileType if it
makes sense to automatically start the plugin for a given filetype. Plugins
must not invoke API methods (or really do anything with non-trivial
side-effects) in global module scope, as the module might be loaded as part
of executing `UpdateRemotePlugins`.

You need to run ``:UpdateRemotePlugins`` in Neovim for changes in the specifications to have effect.
For details see ``:help remote-plugin`` in Neovim.
Expand Down
53 changes: 39 additions & 14 deletions pynvim/plugin/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ def shutdown(self):
self._unload()
self.nvim.stop_loop()

def _wrap_delayed_function(self, cls, delayed_handlers, name, sync,
module_handlers, path, *args):
# delete the delayed handlers to be sure
for handler in delayed_handlers:
method_name = handler._nvim_registered_name
if handler._nvim_rpc_sync:
del self._request_handlers[method_name]
else:
del self._notification_handlers[method_name]
# create an instance of the plugin and pass the nvim object
plugin = cls(self._configure_nvim_for(cls))

# discover handlers in the plugin instance
self._discover_functions(plugin, module_handlers, path, False)

if sync:
self._request_handlers[name](*args)
else:
self._notification_handlers[name](*args)

def _wrap_function(self, fn, sync, decode, nvim_bind, name, *args):
if decode:
args = walk(decode_if_bytes, args, decode)
Expand Down Expand Up @@ -144,7 +164,7 @@ def _load(self, plugins):
module = imp.load_module(name, file, pathname, descr)
handlers = []
self._discover_classes(module, handlers, path)
self._discover_functions(module, handlers, path)
self._discover_functions(module, handlers, path, False)
if not handlers:
error('{} exports no handlers'.format(path))
continue
Expand All @@ -165,7 +185,7 @@ def _unload(self):
for path, plugin in self._loaded.items():
handlers = plugin['handlers']
for handler in handlers:
method_name = handler._nvim_rpc_method_name
method_name = handler._nvim_registered_name
if hasattr(handler, '_nvim_shutdown_hook'):
handler()
elif handler._nvim_rpc_sync:
Expand All @@ -178,31 +198,35 @@ def _unload(self):
def _discover_classes(self, module, handlers, plugin_path):
for _, cls in inspect.getmembers(module, inspect.isclass):
if getattr(cls, '_nvim_plugin', False):
# create an instance of the plugin and pass the nvim object
plugin = cls(self._configure_nvim_for(cls))
# discover handlers in the plugin instance
self._discover_functions(plugin, handlers, plugin_path)
self._discover_functions(cls, handlers, plugin_path, True)

def _discover_functions(self, obj, handlers, plugin_path):
def _discover_functions(self, obj, handlers, plugin_path, delay):
def predicate(o):
return hasattr(o, '_nvim_rpc_method_name')

cls_handlers = []
specs = []
objdecode = getattr(obj, '_nvim_decode', self._decode_default)
for _, fn in inspect.getmembers(obj, predicate):
sync = fn._nvim_rpc_sync
decode = getattr(fn, '_nvim_decode', objdecode)
nvim_bind = None
if fn._nvim_bind:
nvim_bind = self._configure_nvim_for(fn)

method = fn._nvim_rpc_method_name
if fn._nvim_prefix_plugin_path:
method = '{}:{}'.format(plugin_path, method)
sync = fn._nvim_rpc_sync
if delay:
fn_wrapped = partial(self._wrap_delayed_function, obj,
cls_handlers, method, sync,
handlers, plugin_path)
else:
decode = getattr(fn, '_nvim_decode', objdecode)
nvim_bind = None
if fn._nvim_bind:
nvim_bind = self._configure_nvim_for(fn)

fn_wrapped = partial(self._wrap_function, fn,
sync, decode, nvim_bind, method)
fn_wrapped = partial(self._wrap_function, fn,
sync, decode, nvim_bind, method)
self._copy_attributes(fn, fn_wrapped)
fn_wrapped._nvim_registered_name = method
# register in the rpc handler dict
if sync:
if method in self._request_handlers:
Expand All @@ -217,6 +241,7 @@ def predicate(o):
if hasattr(fn, '_nvim_rpc_spec'):
specs.append(fn._nvim_rpc_spec)
handlers.append(fn_wrapped)
cls_handlers.append(fn_wrapped)
if specs:
self._specs[plugin_path] = specs

Expand Down

0 comments on commit d3c389f

Please sign in to comment.