Skip to content

Commit cc24cd2

Browse files
committed
Merge pull request #179 from bfredl/nohook
Refactor: let Nvim be the session wrapper and simplify API remote
2 parents 2db41a5 + a0928e0 commit cc24cd2

15 files changed

+267
-291
lines changed

neovim/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import os
77
import sys
88

9-
from .api import DecodeHook, Nvim, SessionHook
9+
from .api import DecodeHook, Nvim
1010
from .msgpack_rpc import (ErrorResponse, child_session, socket_session,
1111
stdio_session, tcp_session)
1212
from .plugin import (Host, autocmd, command, encoding, function, plugin,
@@ -16,8 +16,7 @@
1616
__all__ = ('tcp_session', 'socket_session', 'stdio_session', 'child_session',
1717
'start_host', 'autocmd', 'command', 'encoding', 'function',
1818
'plugin', 'rpc_export', 'Host', 'DecodeHook', 'Nvim',
19-
'SessionHook', 'shutdown_hook', 'attach', 'setup_logging',
20-
'ErrorResponse')
19+
'shutdown_hook', 'attach', 'setup_logging', 'ErrorResponse')
2120

2221

2322
def start_host(session=None):

neovim/api/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
"""
66

77
from .buffer import Buffer
8-
from .common import DecodeHook, SessionHook
8+
from .common import DecodeHook
99
from .nvim import Nvim, NvimError
1010
from .tabpage import Tabpage
1111
from .window import Window
1212

1313

14-
__all__ = ('Nvim', 'Buffer', 'Window', 'Tabpage', 'NvimError', 'SessionHook',
14+
__all__ = ('Nvim', 'Buffer', 'Window', 'Tabpage', 'NvimError',
1515
'DecodeHook')

neovim/api/buffer.py

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""API for working with Nvim buffers."""
2-
from .common import Remote, RemoteMap
2+
from .common import Remote
33
from ..compat import IS_PYTHON3
44

55

@@ -14,22 +14,11 @@ class Buffer(Remote):
1414

1515
"""A remote Nvim buffer."""
1616

17-
def __init__(self, session, code_data):
18-
"""Initialize from session and code_data immutable object.
19-
20-
The `code_data` contains serialization information required for
21-
msgpack-rpc calls. It must be immutable for Buffer equality to work.
22-
"""
23-
self._session = session
24-
self.code_data = code_data
25-
self.vars = RemoteMap(session, 'buffer_get_var', 'buffer_set_var',
26-
self)
27-
self.options = RemoteMap(session, 'buffer_get_option',
28-
'buffer_set_option', self)
17+
_api_prefix = "buffer_"
2918

3019
def __len__(self):
3120
"""Return the number of lines contained in a Buffer."""
32-
return self._session.request('buffer_line_count', self)
21+
return self.request('buffer_line_count')
3322

3423
def __getitem__(self, idx):
3524
"""Get a buffer line or slice by integer index.
@@ -121,7 +110,7 @@ def append(self, lines, index=-1):
121110

122111
def mark(self, name):
123112
"""Return (row, col) tuple for a named mark."""
124-
return self._session.request('buffer_get_mark', self, name)
113+
return self.request('buffer_get_mark', name)
125114

126115
def range(self, start, end):
127116
"""Return a `Range` object, which represents part of the Buffer."""
@@ -132,34 +121,33 @@ def add_highlight(self, hl_group, line, col_start=0,
132121
"""Add a highlight to the buffer."""
133122
if async is None:
134123
async = (src_id != 0)
135-
return self._session.request('buffer_add_highlight', self, src_id,
136-
hl_group, line, col_start,
137-
col_end, async=async)
124+
return self.request('buffer_add_highlight', src_id, hl_group,
125+
line, col_start, col_end, async=async)
138126

139127
def clear_highlight(self, src_id, line_start=0, line_end=-1, async=True):
140128
"""clear highlights from the buffer."""
141-
self._session.request('buffer_clear_highlight', self, src_id,
142-
line_start, line_end, async=async)
129+
self.request('buffer_clear_highlight', src_id,
130+
line_start, line_end, async=async)
143131

144132
@property
145133
def name(self):
146134
"""Get the buffer name."""
147-
return self._session.request('buffer_get_name', self)
135+
return self.request('buffer_get_name')
148136

149137
@name.setter
150138
def name(self, value):
151139
"""Set the buffer name. BufFilePre/BufFilePost are triggered."""
152-
return self._session.request('buffer_set_name', self, value)
140+
return self.request('buffer_set_name', value)
153141

154142
@property
155143
def valid(self):
156144
"""Return True if the buffer still exists."""
157-
return self._session.request('buffer_is_valid', self)
145+
return self.request('buffer_is_valid')
158146

159147
@property
160148
def number(self):
161149
"""Get the buffer number."""
162-
return self._session.request('buffer_get_number', self)
150+
return self.request('buffer_get_number')
163151

164152

165153
class Range(object):

neovim/api/common.py

Lines changed: 42 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Code shared between the API classes."""
2+
import functools
23

34

45
class Remote(object):
@@ -10,6 +11,20 @@ class Remote(object):
1011
object handle into consideration.
1112
"""
1213

14+
def __init__(self, session, code_data):
15+
"""Initialize from session and code_data immutable object.
16+
17+
The `code_data` contains serialization information required for
18+
msgpack-rpc calls. It must be immutable for Buffer equality to work.
19+
"""
20+
self._session = session
21+
self.code_data = code_data
22+
self.api = RemoteApi(self, self._api_prefix)
23+
self.vars = RemoteMap(self, self._api_prefix + 'get_var',
24+
self._api_prefix + 'set_var')
25+
self.options = RemoteMap(self, self._api_prefix + 'get_option',
26+
self._api_prefix + 'set_option')
27+
1328
def __eq__(self, other):
1429
"""Return True if `self` and `other` are the same object."""
1530
return (hasattr(other, 'code_data') and
@@ -19,6 +34,24 @@ def __hash__(self):
1934
"""Return hash based on remote object id."""
2035
return self.code_data.__hash__()
2136

37+
def request(self, name, *args, **kwargs):
38+
"""Wrapper for nvim.request."""
39+
return self._session.request(name, self, *args, **kwargs)
40+
41+
42+
class RemoteApi(object):
43+
44+
"""Wrapper to allow api methods to be called like python methods."""
45+
46+
def __init__(self, obj, api_prefix):
47+
"""Initialize a RemoteApi with object and api prefix."""
48+
self._obj = obj
49+
self._api_prefix = api_prefix
50+
51+
def __getattr__(self, name):
52+
"""Return wrapper to named api method."""
53+
return functools.partial(self._obj.request, self._api_prefix + name)
54+
2255

2356
class RemoteMap(object):
2457

@@ -31,12 +64,12 @@ class RemoteMap(object):
3164
It is used to provide a dict-like API to vim variables and options.
3265
"""
3366

34-
def __init__(self, session, get_method, set_method, self_obj=None):
67+
def __init__(self, obj, get_method, set_method=None, self_obj=None):
3568
"""Initialize a RemoteMap with session, getter/setter and self_obj."""
36-
self._get = _wrap(session, get_method, self_obj)
69+
self._get = functools.partial(obj.request, get_method)
3770
self._set = None
3871
if set_method:
39-
self._set = _wrap(session, set_method, self_obj)
72+
self._set = functools.partial(obj.request, set_method)
4073

4174
def __getitem__(self, key):
4275
"""Return a map value by key."""
@@ -91,9 +124,9 @@ class RemoteSequence(object):
91124
locally(iteration, indexing, counting, etc).
92125
"""
93126

94-
def __init__(self, session, method, self_obj=None):
127+
def __init__(self, session, method):
95128
"""Initialize a RemoteSequence with session, method and self_obj."""
96-
self._fetch = _wrap(session, method, self_obj)
129+
self._fetch = functools.partial(session.request, method)
97130

98131
def __len__(self):
99132
"""Return the length of the remote sequence."""
@@ -120,48 +153,7 @@ def _identity(obj, session, method, kind):
120153
return obj
121154

122155

123-
class SessionHook(object):
124-
125-
"""Pair of functions to filter objects coming/going from/to Nvim.
126-
127-
Filter functions receive the following arguments:
128-
129-
- obj: The object to process
130-
- session: The current session object
131-
- method: The method name
132-
- kind: Kind of filter, can be one of:
133-
- 'request' for requests coming from Nvim
134-
- 'notification' for notifications coming from Nvim
135-
- 'out-request' for requests going to Nvim
136-
137-
Whatever is returned from the function is used as a replacement for `obj`.
138-
139-
This class also provides a `compose` method for composing hooks.
140-
"""
141-
142-
def __init__(self, from_nvim=_identity, to_nvim=_identity):
143-
"""Initialize a SessionHook with from/to filters."""
144-
self.from_nvim = from_nvim
145-
self.to_nvim = to_nvim
146-
147-
def compose(self, other):
148-
"""Compose two SessionHook instances.
149-
150-
This works by composing the individual from/to filters and creating
151-
a new SessionHook instance with the composed filters.
152-
"""
153-
def comp(f1, f2):
154-
if f1 is _identity:
155-
return f2
156-
if f2 is _identity:
157-
return f1
158-
return lambda o, s, m, k: f1(f2(o, s, m, k), s, m, k)
159-
160-
return SessionHook(comp(other.from_nvim, self.from_nvim),
161-
comp(other.to_nvim, self.to_nvim))
162-
163-
164-
class DecodeHook(SessionHook):
156+
class DecodeHook(object):
165157

166158
"""SessionHook subclass that decodes utf-8 strings coming from Nvim.
167159
@@ -173,9 +165,9 @@ def __init__(self, encoding='utf-8', encoding_errors='strict'):
173165
"""Initialize with encoding and encoding errors policy."""
174166
self.encoding = encoding
175167
self.encoding_errors = encoding_errors
176-
super(DecodeHook, self).__init__(from_nvim=self._decode_if_bytes)
177168

178-
def _decode_if_bytes(self, obj, session, method, kind):
169+
def decode_if_bytes(self, obj):
170+
"""Decode obj if it is bytes."""
179171
if isinstance(obj, bytes):
180172
return obj.decode(self.encoding, errors=self.encoding_errors)
181173
return obj
@@ -185,67 +177,7 @@ def walk(self, obj):
185177
186178
Uses encoding and policy specified in constructor.
187179
"""
188-
return walk(self._decode_if_bytes, obj, None, None, None)
189-
190-
191-
class SessionFilter(object):
192-
193-
"""Wraps a session-like object with a SessionHook instance.
194-
195-
This class can be used as a drop-in replacement for a sessions, the
196-
difference is that a hook is applied to all data passing through a
197-
SessionFilter instance.
198-
"""
199-
200-
def __init__(self, session, hook):
201-
"""Initialize with a Session(or SessionFilter) and a hook.
202-
203-
If `session` is already a SessionFilter, it's hook will be extracted
204-
and composed with `hook`.
205-
"""
206-
if isinstance(session, SessionFilter):
207-
self._hook = session._hook.compose(hook)
208-
self._session = session._session
209-
else:
210-
self._hook = hook
211-
self._session = session
212-
# Both filters are applied to `walk` so objects are transformed
213-
# recursively
214-
self._in = self._hook.from_nvim
215-
self._out = self._hook.to_nvim
216-
217-
def threadsafe_call(self, fn, *args, **kwargs):
218-
"""Wrapper for Session.threadsafe_call."""
219-
self._session.threadsafe_call(fn, *args, **kwargs)
220-
221-
def next_message(self):
222-
"""Wrapper for Session.next_message."""
223-
msg = self._session.next_message()
224-
if msg:
225-
return walk(self._in, msg, self, msg[1], msg[0])
226-
227-
def request(self, name, *args, **kwargs):
228-
"""Wrapper for Session.request."""
229-
args = walk(self._out, args, self, name, 'out-request')
230-
return walk(self._in, self._session.request(name, *args, **kwargs),
231-
self, name, 'out-request')
232-
233-
def run(self, request_cb, notification_cb, setup_cb=None):
234-
"""Wrapper for Session.run."""
235-
def filter_request_cb(name, args):
236-
result = request_cb(self._in(name, self, name, 'request'),
237-
walk(self._in, args, self, name, 'request'))
238-
return walk(self._out, result, self, name, 'request')
239-
240-
def filter_notification_cb(name, args):
241-
notification_cb(self._in(name, self, name, 'notification'),
242-
walk(self._in, args, self, name, 'notification'))
243-
244-
self._session.run(filter_request_cb, filter_notification_cb, setup_cb)
245-
246-
def stop(self):
247-
"""Wrapper for Session.stop."""
248-
self._session.stop()
180+
return walk(self.decode_if_bytes, obj)
249181

250182

251183
def walk(fn, obj, *args):
@@ -256,10 +188,3 @@ def walk(fn, obj, *args):
256188
return dict((walk(fn, k, *args), walk(fn, v, *args)) for k, v in
257189
obj.items())
258190
return fn(obj, *args)
259-
260-
261-
def _wrap(session, method, self_obj):
262-
if self_obj is not None:
263-
return lambda *args: session.request(method, self_obj, *args)
264-
else:
265-
return lambda *args: session.request(method, *args)

0 commit comments

Comments
 (0)