Skip to content

Commit 8da0b84

Browse files
authored
Merge pull request #384 from bfredl/delayplugin
host: do not construct plugin objects on UpdateRemotePlugins
2 parents 9923ee3 + d3c389f commit 8da0b84

File tree

2 files changed

+48
-20
lines changed

2 files changed

+48
-20
lines changed

docs/usage/remote-plugins.rst

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,15 @@ but it can make asynchronous requests, i.e. passing ``async_=True``.
4646

4747
.. note::
4848

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

5659
You need to run ``:UpdateRemotePlugins`` in Neovim for changes in the specifications to have effect.
5760
For details see ``:help remote-plugin`` in Neovim.

pynvim/plugin/host.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,26 @@ def shutdown(self):
7272
self._unload()
7373
self.nvim.stop_loop()
7474

75+
def _wrap_delayed_function(self, cls, delayed_handlers, name, sync,
76+
module_handlers, path, *args):
77+
# delete the delayed handlers to be sure
78+
for handler in delayed_handlers:
79+
method_name = handler._nvim_registered_name
80+
if handler._nvim_rpc_sync:
81+
del self._request_handlers[method_name]
82+
else:
83+
del self._notification_handlers[method_name]
84+
# create an instance of the plugin and pass the nvim object
85+
plugin = cls(self._configure_nvim_for(cls))
86+
87+
# discover handlers in the plugin instance
88+
self._discover_functions(plugin, module_handlers, path, False)
89+
90+
if sync:
91+
self._request_handlers[name](*args)
92+
else:
93+
self._notification_handlers[name](*args)
94+
7595
def _wrap_function(self, fn, sync, decode, nvim_bind, name, *args):
7696
if decode:
7797
args = walk(decode_if_bytes, args, decode)
@@ -144,7 +164,7 @@ def _load(self, plugins):
144164
module = imp.load_module(name, file, pathname, descr)
145165
handlers = []
146166
self._discover_classes(module, handlers, path)
147-
self._discover_functions(module, handlers, path)
167+
self._discover_functions(module, handlers, path, False)
148168
if not handlers:
149169
error('{} exports no handlers'.format(path))
150170
continue
@@ -165,7 +185,7 @@ def _unload(self):
165185
for path, plugin in self._loaded.items():
166186
handlers = plugin['handlers']
167187
for handler in handlers:
168-
method_name = handler._nvim_rpc_method_name
188+
method_name = handler._nvim_registered_name
169189
if hasattr(handler, '_nvim_shutdown_hook'):
170190
handler()
171191
elif handler._nvim_rpc_sync:
@@ -178,31 +198,35 @@ def _unload(self):
178198
def _discover_classes(self, module, handlers, plugin_path):
179199
for _, cls in inspect.getmembers(module, inspect.isclass):
180200
if getattr(cls, '_nvim_plugin', False):
181-
# create an instance of the plugin and pass the nvim object
182-
plugin = cls(self._configure_nvim_for(cls))
183201
# discover handlers in the plugin instance
184-
self._discover_functions(plugin, handlers, plugin_path)
202+
self._discover_functions(cls, handlers, plugin_path, True)
185203

186-
def _discover_functions(self, obj, handlers, plugin_path):
204+
def _discover_functions(self, obj, handlers, plugin_path, delay):
187205
def predicate(o):
188206
return hasattr(o, '_nvim_rpc_method_name')
189207

208+
cls_handlers = []
190209
specs = []
191210
objdecode = getattr(obj, '_nvim_decode', self._decode_default)
192211
for _, fn in inspect.getmembers(obj, predicate):
193-
sync = fn._nvim_rpc_sync
194-
decode = getattr(fn, '_nvim_decode', objdecode)
195-
nvim_bind = None
196-
if fn._nvim_bind:
197-
nvim_bind = self._configure_nvim_for(fn)
198-
199212
method = fn._nvim_rpc_method_name
200213
if fn._nvim_prefix_plugin_path:
201214
method = '{}:{}'.format(plugin_path, method)
215+
sync = fn._nvim_rpc_sync
216+
if delay:
217+
fn_wrapped = partial(self._wrap_delayed_function, obj,
218+
cls_handlers, method, sync,
219+
handlers, plugin_path)
220+
else:
221+
decode = getattr(fn, '_nvim_decode', objdecode)
222+
nvim_bind = None
223+
if fn._nvim_bind:
224+
nvim_bind = self._configure_nvim_for(fn)
202225

203-
fn_wrapped = partial(self._wrap_function, fn,
204-
sync, decode, nvim_bind, method)
226+
fn_wrapped = partial(self._wrap_function, fn,
227+
sync, decode, nvim_bind, method)
205228
self._copy_attributes(fn, fn_wrapped)
229+
fn_wrapped._nvim_registered_name = method
206230
# register in the rpc handler dict
207231
if sync:
208232
if method in self._request_handlers:
@@ -217,6 +241,7 @@ def predicate(o):
217241
if hasattr(fn, '_nvim_rpc_spec'):
218242
specs.append(fn._nvim_rpc_spec)
219243
handlers.append(fn_wrapped)
244+
cls_handlers.append(fn_wrapped)
220245
if specs:
221246
self._specs[plugin_path] = specs
222247

0 commit comments

Comments
 (0)