1 """Code shared between the API classes."""
4 from msgpack import unpackb
6 from pynvim.compat import unicode_errors_default
11 class NvimError(Exception):
17 """Base class for Nvim objects(buffer/window/tabpage).
19 Each type of object has it's own specialized class with API wrappers around
20 the msgpack-rpc session. This implements equality which takes the remote
21 object handle into consideration.
24 def __init__(self, session, code_data):
25 """Initialize from session and code_data immutable object.
27 The `code_data` contains serialization information required for
28 msgpack-rpc calls. It must be immutable for Buffer equality to work.
30 self._session = session
31 self.code_data = code_data
32 self.handle = unpackb(code_data[1])
33 self.api = RemoteApi(self, self._api_prefix)
34 self.vars = RemoteMap(self, self._api_prefix + 'get_var',
35 self._api_prefix + 'set_var',
36 self._api_prefix + 'del_var')
37 self.options = RemoteMap(self, self._api_prefix + 'get_option',
38 self._api_prefix + 'set_option')
41 """Get text representation of the object."""
42 return '<%s(handle=%r)>' % (
43 self.__class__.__name__,
47 def __eq__(self, other):
48 """Return True if `self` and `other` are the same object."""
49 return (hasattr(other, 'code_data')
50 and other.code_data == self.code_data)
53 """Return hash based on remote object id."""
54 return self.code_data.__hash__()
56 def request(self, name, *args, **kwargs):
57 """Wrapper for nvim.request."""
58 return self._session.request(name, self, *args, **kwargs)
61 class RemoteApi(object):
63 """Wrapper to allow api methods to be called like python methods."""
65 def __init__(self, obj, api_prefix):
66 """Initialize a RemoteApi with object and api prefix."""
68 self._api_prefix = api_prefix
70 def __getattr__(self, name):
71 """Return wrapper to named api method."""
72 return functools.partial(self._obj.request, self._api_prefix + name)
75 def transform_keyerror(exc):
76 if isinstance(exc, NvimError):
77 if exc.args[0].startswith('Key not found:'):
78 return KeyError(exc.args[0])
79 if exc.args[0].startswith('Invalid option name:'):
80 return KeyError(exc.args[0])
84 class RemoteMap(object):
85 """Represents a string->object map stored in Nvim.
87 This is the dict counterpart to the `RemoteSequence` class, but it is used
88 as a generic way of retrieving values from the various map-like data
89 structures present in Nvim.
91 It is used to provide a dict-like API to vim variables and options.
97 def __init__(self, obj, get_method, set_method=None, del_method=None):
98 """Initialize a RemoteMap with session, getter/setter."""
99 self._get = functools.partial(obj.request, get_method)
101 self._set = functools.partial(obj.request, set_method)
103 self._del = functools.partial(obj.request, del_method)
105 def __getitem__(self, key):
106 """Return a map value by key."""
108 return self._get(key)
109 except NvimError as exc:
110 raise transform_keyerror(exc)
112 def __setitem__(self, key, value):
113 """Set a map value by key(if the setter was provided)."""
115 raise TypeError('This dict is read-only')
116 self._set(key, value)
118 def __delitem__(self, key):
119 """Delete a map value by associating None with the key."""
121 raise TypeError('This dict is read-only')
123 return self._del(key)
124 except NvimError as exc:
125 raise transform_keyerror(exc)
127 def __contains__(self, key):
128 """Check if key is present in the map."""
135 def get(self, key, default=None):
136 """Return value for key if present, else a default value."""
138 return self.__getitem__(key)
143 class RemoteSequence(object):
145 """Represents a sequence of objects stored in Nvim.
147 This class is used to wrap msgapck-rpc functions that work on Nvim
148 sequences(of lines, buffers, windows and tabpages) with an API that
149 is similar to the one provided by the python-vim interface.
151 For example, the 'windows' property of the `Nvim` class is a RemoteSequence
152 sequence instance, and the expression `nvim.windows[0]` is translated to
153 session.request('nvim_list_wins')[0].
155 One important detail about this class is that all methods will fetch the
156 sequence into a list and perform the necessary manipulation
157 locally(iteration, indexing, counting, etc).
160 def __init__(self, session, method):
161 """Initialize a RemoteSequence with session, method."""
162 self._fetch = functools.partial(session.request, method)
165 """Return the length of the remote sequence."""
166 return len(self._fetch())
168 def __getitem__(self, idx):
169 """Return a sequence item by index."""
170 if not isinstance(idx, slice):
171 return self._fetch()[idx]
172 return self._fetch()[idx.start:idx.stop]
175 """Return an iterator for the sequence."""
176 items = self._fetch()
180 def __contains__(self, item):
181 """Check if an item is present in the sequence."""
182 return item in self._fetch()
185 def _identity(obj, session, method, kind):
189 def decode_if_bytes(obj, mode=True):
190 """Decode obj if it is bytes."""
192 mode = unicode_errors_default
193 if isinstance(obj, bytes):
194 return obj.decode("utf-8", errors=mode)
198 def walk(fn, obj, *args, **kwargs):
199 """Recursively walk an object graph applying `fn`/`args` to objects."""
200 if type(obj) in [list, tuple]:
201 return list(walk(fn, o, *args) for o in obj)
202 if type(obj) is dict:
203 return dict((walk(fn, k, *args), walk(fn, v, *args)) for k, v in
205 return fn(obj, *args, **kwargs)