Completed
Pull Request — master (#179)
by Björn
40:43 queued 15:44
created

neovim.api.RemoteMap   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 48
Duplicated Lines 0 %

Test Coverage

Coverage 62.96%
Metric Value
dl 0
loc 48
ccs 17
cts 27
cp 0.6296
rs 10
wmc 11
1
"""Code shared between the API classes."""
2
import functools
3
4 6
5
class Remote(object):
6
7
    """Base class for Nvim objects(buffer/window/tabpage).
8
9
    Each type of object has it's own specialized class with API wrappers around
10
    the msgpack-rpc session. This implements equality which takes the remote
11
    object handle into consideration.
12
    """
13 6
14
    def __init__(self, session, code_data):
15 6
        """Initialize from session and code_data immutable object.
16
17
        The `code_data` contains serialization information required for
18 6
        msgpack-rpc calls. It must be immutable for Buffer equality to work.
19
        """
20 6
        self._session = session
21
        self.code_data = code_data
22
        self.api = RemoteApi(self, self._api_prefix)
23 6
        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
28
    def __eq__(self, other):
29
        """Return True if `self` and `other` are the same object."""
30
        return (hasattr(other, 'code_data') and
31
                other.code_data == self.code_data)
32
33
    def __hash__(self):
34 6
        """Return hash based on remote object id."""
35
        return self.code_data.__hash__()
36 6
37 6
    def request(self, name, *args, **kwargs):
38 6
        """Wrapper for nvim.request."""
39 6
        return self._session.request(name, self, *args, **kwargs)
40
41 6
42
class RemoteApi(object):
43 6
44
        """Wrapper to allow api methods to be called like python methods."""
45 6
46
    def __init__(self, obj, api_prefix):
47 6
        """Initialize a RemoteApi with object and api prefix."""
48
        self._obj = obj
49 6
        self._api_prefix = api_prefix
50
51 6
    def __getattr__(self, name):
52
        """Return wrapper to named api method."""
53 6
        return functools.partial(self._obj.request, self._api_prefix + name)
54 6
55
56
class RemoteMap(object):
57 6
58
    """Represents a string->object map stored in Nvim.
59 6
60
    This is the dict counterpart to the `RemoteSequence` class, but it is used
61
    as a generic way of retrieving values from the various map-like data
62
    structures present in Nvim.
63
64
    It is used to provide a dict-like API to vim variables and options.
65 6
    """
66
67
    def __init__(self, obj, get_method, set_method=None, self_obj=None):
68
        """Initialize a RemoteMap with session, getter/setter and self_obj."""
69
        self._get = functools.partial(obj.request, get_method)
70
        self._set = None
71
        if set_method:
72
            self._set = functools.partial(obj.request, set_method)
73 6
74
    def __getitem__(self, key):
75
        """Return a map value by key."""
76
        return self._get(key)
77
78
    def __setitem__(self, key, value):
79
        """Set a map value by key(if the setter was provided)."""
80
        if not self._set:
81
            raise TypeError('This dict is read-only')
82
        self._set(key, value)
83
84
    def __delitem__(self, key):
85
        """Delete a map value by associating None with the key."""
86
        if not self._set:
87
            raise TypeError('This dict is read-only')
88
        return self._set(key, None)
89
90
    def __contains__(self, key):
91
        """Check if key is present in the map."""
92
        try:
93
            self._get(key)
94 6
            return True
95
        except Exception:
96 6
            return False
97
98 6
    def get(self, key, default=None):
99
        """Return value for key if present, else a default value."""
100 6
        try:
101
            return self._get(key)
102 6
        except Exception:
103
            return default
104 6
105 6
106
class RemoteSequence(object):
107
108 6
    """Represents a sequence of objects stored in Nvim.
109
110 6
    This class is used to wrap msgapck-rpc functions that work on Nvim
111 6
    sequences(of lines, buffers, windows and tabpages) with an API that
112 6
    is similar to the one provided by the python-vim interface.
113
114 6
    For example, the 'buffers' property of the `Nvim class is a RemoteSequence
115
    sequence instance, and the expression `nvim.buffers[0]` is translated to
116 6
    session.request('vim_get_buffers')[0].
117
118
    It can also receive an optional self_obj that will be passed as first
119 6
    argument of the request. For example, `tabpage.windows[0]` is translated
120
    to: session.request('tabpage_get_windows', tabpage_instance)[0].
121
122
    One important detail about this class is that all methods will fetch the
123 6
    sequence into a list and perform the necessary manipulation
124
    locally(iteration, indexing, counting, etc).
125
    """
126
127
    def __init__(self, session, method):
128
        """Initialize a RemoteSequence with session, method and self_obj."""
129
        self._fetch = functools.partial(session.request, method)
130
131
    def __len__(self):
132
        """Return the length of the remote sequence."""
133
        return len(self._fetch())
134
135
    def __getitem__(self, idx):
136
        """Return a sequence item by index."""
137
        if not isinstance(idx, slice):
138
            return self._fetch()[idx]
139
        return self._fetch()[idx.start:idx.stop]
140
141
    def __iter__(self):
142 6
        """Return an iterator for the sequence."""
143
        items = self._fetch()
144 6
        for item in items:
145 6
            yield item
146
147 6
    def __contains__(self, item):
148
        """Check if an item is present in the sequence."""
149
        return item in self._fetch()
150
151
152
def _identity(obj, session, method, kind):
153 3
    return obj
154 3
155 3
156 3
class DecodeHook(object):
157
158 3
    """SessionHook subclass that decodes utf-8 strings coming from Nvim.
159
160 3
    This class is useful for python3, where strings are now unicode by
161
    default(byte strings need to be prefixed with "b").
162
    """
163
164 6
    def __init__(self, encoding='utf-8', encoding_errors='strict'):
165
        """Initialize with encoding and encoding errors policy."""
166
        self.encoding = encoding
167
        self.encoding_errors = encoding_errors
168
169
    def decode_if_bytes(self, obj):
170
        if isinstance(obj, bytes):
171
            return obj.decode(self.encoding, errors=self.encoding_errors)
172 6
        return obj
173
174 3
    def walk(self, obj):
175 3
        """Decode bytes found in obj (any msgpack object).
176 3
177
        Uses encoding and policy specified in constructor.
178 6
        """
179 3
        return walk(self.decode_if_bytes, obj)
180 3
181 3
182
def walk(fn, obj, *args):
183 6
    """Recursively walk an object graph applying `fn`/`args` to objects."""
184
    if type(obj) in [list, tuple]:
185
        return list(walk(fn, o, *args) for o in obj)
186
    if type(obj) is dict:
187
        return dict((walk(fn, k, *args), walk(fn, v, *args)) for k, v in
188
                    obj.items())
189
    return fn(obj, *args)
190