Skip to content

Commit f5f4e62

Browse files
committed
session: allow polling on a provided fd
1 parent b8bb7c2 commit f5f4e62

File tree

7 files changed

+111
-11
lines changed

7 files changed

+111
-11
lines changed

neovim/api/nvim.py

+38-9
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,20 @@ def new_highlight_source(self):
322322
"""Return new src_id for use with Buffer.add_highlight."""
323323
return self.current.buffer.add_highlight("", 0, src_id=0)
324324

325+
def _error_wrapper(self, fn, call_point, *args, **kwargs):
326+
if fn is None:
327+
return None
328+
def handler():
329+
try:
330+
fn(*args, **kwargs)
331+
except Exception as err:
332+
msg = ("error caught while executing async callback:\n"
333+
"{0!r}\n{1}\n \nthe call was requested at\n{2}"
334+
.format(err, format_exc_skip(1, 5), call_point))
335+
self._err_cb(msg)
336+
raise
337+
return handler
338+
325339
def async_call(self, fn, *args, **kwargs):
326340
"""Schedule `fn` to be called by the event loop soon.
327341
@@ -333,18 +347,33 @@ def async_call(self, fn, *args, **kwargs):
333347
that shouldn't block neovim.
334348
"""
335349
call_point = ''.join(format_stack(None, 5)[:-1])
350+
handler = self._error_wrapper(fn, call_point, *args, **kwargs)
336351

337-
def handler():
338-
try:
339-
fn(*args, **kwargs)
340-
except Exception as err:
341-
msg = ("error caught while executing async callback:\n"
342-
"{0!r}\n{1}\n \nthe call was requested at\n{2}"
343-
.format(err, format_exc_skip(1, 5), call_point))
344-
self._err_cb(msg)
345-
raise
346352
self._session.threadsafe_call(handler)
347353

354+
def poll_fd(self, fd, on_readable=None, on_writable=None, greenlet=True):
355+
"""
356+
Invoke callbacks when the fd is ready for reading and/or writing. if
357+
`on_readable` is not None, it should be callback, which will be invoked
358+
(with no arguments) when the fd is ready for writing. Similarily if
359+
`on_writable` is not None it will be invoked when the fd is ready for
360+
writing.
361+
362+
Only one callback (of each kind) can be registered on the same fd at a
363+
time. If both readability and writability should be monitored, both
364+
callbacks must be registered by the same `poll_fd` call.
365+
366+
By default, the function is invoked in a greenlet, just like a callback
367+
scheduled by async_call.
368+
369+
Returns a function that deactivates the callback(s).
370+
"""
371+
call_point = ''.join(format_stack(None, 5)[:-1])
372+
on_readable = self._error_wrapper(on_readable, call_point)
373+
on_writable = self._error_wrapper(on_writable, call_point)
374+
return self._session.poll_fd(fd, on_readable, on_writable, greenlet)
375+
376+
348377

349378
class Buffers(object):
350379

neovim/msgpack_rpc/async_session.py

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ def threadsafe_call(self, fn):
3232
"""Wrapper around `MsgpackStream.threadsafe_call`."""
3333
self._msgpack_stream.threadsafe_call(fn)
3434

35+
def poll_fd(self, fd, on_readable, on_writable):
36+
"""Wrapper around `BaseEventLoop.poll_fd`."""
37+
return self._msgpack_stream.poll_fd(fd, on_readable, on_writable)
38+
3539
def request(self, method, args, response_cb):
3640
"""Send a msgpack-rpc request to Nvim.
3741

neovim/msgpack_rpc/event_loop/asyncio.py

+12
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ def _stop(self):
113113
def _threadsafe_call(self, fn):
114114
self._loop.call_soon_threadsafe(fn)
115115

116+
def _poll_fd(self, fd, on_readable, on_writable):
117+
if on_readable is not None:
118+
self._loop.add_reader(fd, on_readable)
119+
if on_writable is not None:
120+
self._loop.add_writer(fd, on_writable)
121+
def cancel():
122+
if on_readable is not None:
123+
self._loop.remove_reader(fd)
124+
if on_writable is not None:
125+
self._loop.remove_writer(fd)
126+
return cancel
127+
116128
def _setup_signals(self, signals):
117129
if os.name == 'nt':
118130
# add_signal_handler is not supported in win32

neovim/msgpack_rpc/event_loop/base.py

+18
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@ def threadsafe_call(self, fn):
121121
"""
122122
self._threadsafe_call(fn)
123123

124+
def poll_fd(self, fd, on_readable=None, on_writable=None):
125+
"""
126+
Invoke callbacks when the fd is ready for reading and/or writing. if
127+
`on_readable` is not None, it should be callback, which will be invoked
128+
(with no arguments) when the fd is ready for writing. Similarily if
129+
`on_writable` is not None it will be invoked when the fd is ready for
130+
writing.
131+
132+
Only one callback (of each kind) can be registered on the same fd at a
133+
time. If both readability and writability should be monitored, both
134+
callbacks must be registered by the same `poll_fd` call.
135+
136+
Returns a function that deactivates the callback(s).
137+
"""
138+
if on_readable is None and on_writable is None:
139+
raise ValueError("poll_fd: At least one of `on_readable` and `on_writable` must be present")
140+
return self._poll_fd(fd, on_readable, on_writable)
141+
124142
def run(self, data_cb):
125143
"""Run the event loop."""
126144
if self._error:

neovim/msgpack_rpc/event_loop/uv.py

+16
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ def _on_async(self, handle):
106106
while self._callbacks:
107107
self._callbacks.popleft()()
108108

109+
def _poll_fd(self, fd, on_readable, on_writable):
110+
poll = pyuv.Poll(self._loop, fd)
111+
events = 0
112+
if on_readable is not None:
113+
events |= pyuv.UV_READABLE
114+
if on_writable is not None:
115+
events |= pyuv.UV_WRITABLE
116+
def callback(poll_handle, evts, errorno):
117+
if evts & pyuv.UV_READABLE:
118+
on_readable()
119+
if evts & pyuv.UV_WRITABLE:
120+
on_writable()
121+
122+
poll.start(events, callback)
123+
return poll.stop
124+
109125
def _setup_signals(self, signals):
110126
self._signal_handles = []
111127

neovim/msgpack_rpc/msgpack_stream.py

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ def threadsafe_call(self, fn):
2828
"""Wrapper around `BaseEventLoop.threadsafe_call`."""
2929
self._event_loop.threadsafe_call(fn)
3030

31+
def poll_fd(self, fd, on_readable, on_writable):
32+
"""Wrapper around `BaseEventLoop.poll_fd`."""
33+
return self._event_loop.poll_fd(fd, on_readable, on_writable)
34+
3135
def send(self, msg):
3236
"""Queue `msg` for sending to Nvim."""
3337
debug('sent %s', msg)

neovim/msgpack_rpc/session.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ def __init__(self, async_session):
2828
self._is_running = False
2929
self._setup_exception = None
3030

31-
def threadsafe_call(self, fn, *args, **kwargs):
32-
"""Wrapper around `AsyncSession.threadsafe_call`."""
31+
def _wrap_greenlet(self, fn, *args, **kwargs):
32+
if fn is None:
33+
return None
34+
3335
def handler():
3436
try:
3537
fn(*args, **kwargs)
@@ -41,8 +43,23 @@ def greenlet_wrapper():
4143
gr = greenlet.greenlet(handler)
4244
gr.switch()
4345

46+
return greenlet_wrapper
47+
48+
def threadsafe_call(self, fn, *args, **kwargs):
49+
"""Wrapper around `AsyncSession.threadsafe_call`."""
50+
51+
greenlet_wrapper = self._wrap_greenlet(fn, *args, **kwargs)
4452
self._async_session.threadsafe_call(greenlet_wrapper)
4553

54+
def poll_fd(self, fd, on_readable, on_writable, greenlet=True):
55+
"""Wrapper around `AsyncSession.threadsafe_call`."""
56+
if greenlet:
57+
on_readable = self._wrap_greenlet(on_readable)
58+
on_writable = self._wrap_greenlet(on_writable)
59+
60+
return self._async_session.poll_fd(fd, on_readable, on_writable)
61+
62+
4663
def next_message(self):
4764
"""Block until a message(request or notification) is available.
4865

0 commit comments

Comments
 (0)