Completed
Pull Request — master (#294)
by Björn
38:36 queued 13:35
created

Nvim.eval()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
ccs 1
cts 1
cp 1
crap 1
1
"""Main Nvim interface."""
2 5
import functools
3 5
import os
4 5
import sys
5
from traceback import format_stack
6 5
7
from msgpack import ExtType
0 ignored issues
show
Configuration introduced by
The import msgpack could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
8 5
9
from .buffer import Buffer
10 5
from .common import (Remote, RemoteApi, RemoteMap, RemoteSequence,
11 5
                     decode_if_bytes, walk)
12
from .tabpage import Tabpage
13 5
from .window import Window
14 5
from ..compat import IS_PYTHON3
15 5
from ..util import Version, format_exc_skip
16 5
17
__all__ = ('Nvim')
18 5
19
20
os_chdir = os.chdir
21 5
22
23
class Nvim(object):
24 5
25
    """Class that represents a remote Nvim instance.
26
27
    This class is main entry point to Nvim remote API, it is a wrapper
28
    around Session instances.
29
30
    The constructor of this class must not be called directly. Instead, the
31
    `from_session` class method should be used to create the first instance
32
    from a raw `Session` instance.
33
34
    Subsequent instances for the same session can be created by calling the
35
    `with_decode` instance method to change the decoding behavior or
36
    `SubClass.from_nvim(nvim)` where `SubClass` is a subclass of `Nvim`, which
37
    is useful for having multiple `Nvim` objects that behave differently
38
    without one affecting the other.
39
    """
40
41
    @classmethod
42 5
    def from_session(cls, session):
43
        """Create a new Nvim instance for a Session instance.
44
45
        This method must be called to create the first Nvim instance, since it
46
        queries Nvim metadata for type information and sets a SessionHook for
47
        creating specialized objects from Nvim remote handles.
48
        """
49
        session.error_wrapper = lambda e: NvimError(e[1])
50 5
        channel_id, metadata = session.request(b'vim_get_api_info')
51 5
52
        if IS_PYTHON3:
53 5
            # decode all metadata strings for python3
54
            metadata = walk(decode_if_bytes, metadata)
55 5
56
        types = {
57 5
            metadata['types']['Buffer']['id']: Buffer,
58
            metadata['types']['Window']['id']: Window,
59
            metadata['types']['Tabpage']['id']: Tabpage,
60
        }
61
62
        return cls(session, channel_id, metadata, types)
63 5
64
    @classmethod
65 5
    def from_nvim(cls, nvim):
66
        """Create a new Nvim instance from an existing instance."""
67
        return cls(nvim._session, nvim.channel_id, nvim.metadata,
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _session was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
68
                   nvim.types, nvim._decode, nvim._err_cb)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _decode was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
Coding Style Best Practice introduced by
It seems like _err_cb was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
69
70
    def __init__(self, session, channel_id, metadata, types,
71 5
                 decode=False, err_cb=None):
72
        """Initialize a new Nvim instance. This method is module-private."""
73
        self._session = session
74 5
        self.channel_id = channel_id
75 5
        self.metadata = metadata
76 5
        version = metadata.get("version", {"api_level": 0})
77 5
        self.version = Version(**version)
78 5
        self.types = types
79 5
        self.api = RemoteApi(self, 'nvim_')
80 5
        self.vars = RemoteMap(self, 'nvim_get_var', 'nvim_set_var')
81 5
        self.vvars = RemoteMap(self, 'nvim_get_vvar', None)
82 5
        self.options = RemoteMap(self, 'nvim_get_option', 'nvim_set_option')
83 5
        self.buffers = Buffers(self)
84 5
        self.windows = RemoteSequence(self, 'nvim_list_wins')
85 5
        self.tabpages = RemoteSequence(self, 'nvim_list_tabpages')
86 5
        self.current = Current(self)
87 5
        self.session = CompatibilitySession(self)
88 5
        self.funcs = Funcs(self)
89 5
        self.error = NvimError
90 5
        self._decode = decode
91 5
        self._err_cb = err_cb
92 5
93
        # only on python3.4+ we expose asyncio
94 5
        if IS_PYTHON3:
95 5
            self.loop = self._session.loop._loop
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _loop was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
96 5
97 5
    def _from_nvim(self, obj, decode=None):
98 5
        if decode is None:
99 5
            decode = self._decode
100 5
        if type(obj) is ExtType:
101 3
            cls = self.types[obj.code]
102 5
            return cls(self, (obj.code, obj.data))
103
        if decode:
104 5
            obj = decode_if_bytes(obj, decode)
105 5
        return obj
106 5
107 5
    def _to_nvim(self, obj):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
108
        if isinstance(obj, Remote):
109 5
            return ExtType(*obj.code_data)
110
        return obj
111
112
    def request(self, name, *args, **kwargs):
113
        r"""Send an API request or notification to nvim.
114
115
        It is rarely needed to call this function directly, as most API
116
        functions have python wrapper functions. The `api` object can
117
        be also be used to call API functions as methods:
118
119
            vim.api.err_write('ERROR\n', async=True)
120
            vim.current.buffer.api.get_mark('.')
121
122
        is equivalent to
123
124
            vim.request('nvim_err_write', 'ERROR\n', async=True)
125
            vim.request('nvim_buf_get_mark', vim.current.buffer, '.')
126
127
128
        Normally a blocking request will be sent.  If the `async` flag is
129 5
        present and True, a asynchronous notification is sent instead. This
130 5
        will never block, and the return value or error is ignored.
131 5
        """
132 5
        decode = kwargs.pop('decode', self._decode)
133
        args = walk(self._to_nvim, args)
134 5
        res = self._session.request(name, *args, **kwargs)
135
        return walk(self._from_nvim, res, decode=decode)
136
137
    def next_message(self):
138
        """Block until a message(request or notification) is available.
139
140 5
        If any messages were previously enqueued, return the first in queue.
141 5
        If not, run the event loop until one is received.
142 5
        """
143
        msg = self._session.next_message()
144 5
        if msg:
145
            return walk(self._from_nvim, msg)
146
147
    def run_loop(self, request_cb, notification_cb,
148
                 setup_cb=None, err_cb=None):
149
        """Run the event loop to receive requests and notifications from Nvim.
150
151 5
        This should not be called from a plugin running in the host, which
152 5
        already runs the loop and dispatches events to plugins.
153 5
        """
154
        if err_cb is None:
155 5
            err_cb = sys.stderr.write
156 5
        self._err_cb = err_cb
157 5
158 5
        def filter_request_cb(name, args):
159 5
            name = self._from_nvim(name)
160
            args = walk(self._from_nvim, args)
161
            try:
162
                result = request_cb(name, args)
163
            except Exception:
164
                msg = ("error caught in request handler '{} {}'\n{}\n\n"
165 5
                       .format(name, args, format_exc_skip(1)))
166
                self._err_cb(msg)
167 5
                raise
168
            return walk(self._to_nvim, result)
169
170
        def filter_notification_cb(name, args):
171
            name = self._from_nvim(name)
172
            args = walk(self._from_nvim, args)
173
            try:
174
                notification_cb(name, args)
175
            except Exception:
176
                msg = ("error caught in notification handler '{} {}'\n{}\n\n"
177
                       .format(name, args, format_exc_skip(1)))
178 5
                self._err_cb(msg)
179
                raise
180 5
181
        self._session.run(filter_request_cb, filter_notification_cb, setup_cb)
182 5
183
    def stop_loop(self):
184 5
        """Stop the event loop being started with `run_loop`."""
185
        self._session.stop()
186 5
187
    def with_decode(self, decode=True):
188
        """Initialize a new Nvim instance."""
189 5
        return Nvim(self._session, self.channel_id,
190
                    self.metadata, self.types, decode, self._err_cb)
191
192
    def ui_attach(self, width, height, rgb):
193
        """Register as a remote UI.
194
195
        After this method is called, the client will receive redraw
196
        notifications.
197 5
        """
198
        return self.request('ui_attach', width, height, rgb)
199
200
    def ui_detach(self):
201 5
        """Unregister as a remote UI."""
202
        return self.request('ui_detach')
203
204
    def ui_try_resize(self, width, height):
205
        """Notify nvim that the client window has resized.
206
207
        If possible, nvim will send a redraw request to resize.
208 5
        """
209
        return self.request('ui_try_resize', width, height)
210 5
211
    def subscribe(self, event):
212 5
        """Subscribe to a Nvim event."""
213
        return self.request('nvim_subscribe', event)
214 5
215
    def unsubscribe(self, event):
216 5
        """Unsubscribe to a Nvim event."""
217
        return self.request('nvim_unsubscribe', event)
218 5
219
    def command(self, string, **kwargs):
220 5
        """Execute a single ex command."""
221
        return self.request('nvim_command', string, **kwargs)
222
223
    def command_output(self, string):
224 5
        """Execute a single ex command and return the output."""
225
        return self.request('nvim_command_output', string)
226 5
227
    def eval(self, string, **kwargs):
228 5
        """Evaluate a vimscript expression."""
229
        return self.request('nvim_eval', string, **kwargs)
230 5
231
    def call(self, name, *args, **kwargs):
232 5
        """Call a vimscript function."""
233
        return self.request('nvim_call_function', name, args, **kwargs)
234
235
    def strwidth(self, string):
236
        """Return the number of display cells `string` occupies.
237 5
238
        Tab is counted as one cell.
239 5
        """
240
        return self.request('nvim_strwidth', string)
241
242
    def list_runtime_paths(self):
243 5
        """Return a list of paths contained in the 'runtimepath' option."""
244
        return self.request('nvim_list_runtime_paths')
245
246
    def foreach_rtp(self, cb):
247
        """Invoke `cb` for each path in 'runtimepath'.
248
249
        Call the given callable for each path in 'runtimepath' until either
250
        callable returns something but None, the exception is raised or there
251
        are no longer paths. If stopped in case callable returned non-None,
252
        vim.foreach_rtp function returns the value returned by callable.
253
        """
254
        for path in self.request('nvim_list_runtime_paths'):
255
            try:
256
                if cb(path) is not None:
257
                    break
258 5
            except Exception:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
259
                break
260 5
261 5
    def chdir(self, dir_path):
262
        """Run os.chdir, then all appropriate vim stuff."""
263 5
        os_chdir(dir_path)
264
        return self.request('nvim_set_current_dir', dir_path)
265
266
    def feedkeys(self, keys, options='', escape_csi=True):
267
        """Push `keys` to Nvim user input buffer.
268
269
        Options can be a string with the following character flags:
270
        - 'm': Remap keys. This is default.
271
        - 'n': Do not remap keys.
272
        - 't': Handle keys as if typed; otherwise they are handled as if coming
273
               from a mapping. This matters for undo, opening folds, etc.
274 5
        """
275
        return self.request('nvim_feedkeys', keys, options, escape_csi)
276
277
    def input(self, bytes):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in bytes.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
278
        """Push `bytes` to Nvim low level input buffer.
279
280
        Unlike `feedkeys()`, this uses the lowest level input buffer and the
281
        call is not deferred. It returns the number of bytes actually
282 5
        written(which can be less than what was requested if the buffer is
283
        full).
284 5
        """
285
        return self.request('nvim_input', bytes)
286
287
    def replace_termcodes(self, string, from_part=False, do_lt=True,
288
                          special=True):
289
        r"""Replace any terminal code strings by byte sequences.
290
291
        The returned sequences are Nvim's internal representation of keys,
292
        for example:
293
294
        <esc> -> '\x1b'
295
        <cr>  -> '\r'
296
        <c-l> -> '\x0c'
297
        <up>  -> '\x80ku'
298
299
        The returned sequences can be used as input to `feedkeys`.
300
        """
301 5
        return self.request('nvim_replace_termcodes', string,
302
                            from_part, do_lt, special)
303
304
    def out_write(self, msg, **kwargs):
305 5
        """Print `msg` as a normal message."""
306
        return self.request('nvim_out_write', msg, **kwargs)
307 2
308
    def err_write(self, msg, **kwargs):
309 5
        """Print `msg` as an error message."""
310
        return self.request('nvim_err_write', msg, **kwargs)
311
312
    def quit(self, quit_command='qa!'):
313
        """Send a quit command to Nvim.
314
315
        By default, the quit command is 'qa!' which will make Nvim quit without
316
        saving anything.
317
        """
318
        try:
319
            self.command(quit_command)
320
        except IOError:
0 ignored issues
show
Unused Code introduced by
This except handler seems to be unused and could be removed.

Except handlers which only contain pass and do not have an else clause can usually simply be removed:

try:
    raises_exception()
except:  # Could be removed
    pass
Loading history...
321
            # sending a quit command will raise an IOError because the
322
            # connection is closed before a response is received. Safe to
323 5
            # ignore it.
324
            pass
325
326
    def new_highlight_source(self):
327 5
        """Return new src_id for use with Buffer.add_highlight."""
328
        return self.current.buffer.add_highlight("", 0, src_id=0)
329
330
    def async_call(self, fn, *args, **kwargs):
331
        """Schedule `fn` to be called by the event loop soon.
332
333
        This function is thread-safe, and is the only way code not
334
        on the main thread could interact with nvim api objects.
335
336
        This function can also be called in a synchronous
337 5
        event handler, just before it returns, to defer execution
338
        that shouldn't block neovim.
339 5
        """
340 5
        call_point = ''.join(format_stack(None, 5)[:-1])
341 5
342
        def handler():
343 1
            try:
344
                fn(*args, **kwargs)
345
            except Exception as err:
346 1
                msg = ("error caught while executing async callback:\n"
347
                       "{!r}\n{}\n \nthe call was requested at\n{}"
348 5
                       .format(err, format_exc_skip(1), call_point))
349
                self._err_cb(msg)
350
                raise
351 5
        self._session.threadsafe_call(handler)
352
353
354
class Buffers(object):
355
356
    """Remote NVim buffers.
357
358
    Currently the interface for interacting with remote NVim buffers is the
359
    `nvim_list_bufs` msgpack-rpc function. Most methods fetch the list of
360
    buffers from NVim.
361
362 5
    Conforms to *python-buffers*.
363
    """
364 5
365
    def __init__(self, nvim):
366 5
        """Initialize a Buffers object with Nvim object `nvim`."""
367
        self._fetch_buffers = nvim.api.list_bufs
368 5
369
    def __len__(self):
370 5
        """Return the count of buffers."""
371
        return len(self._fetch_buffers())
372 5
373 5
    def __getitem__(self, number):
374 5
        """Return the Buffer object matching buffer number `number`."""
375
        for b in self._fetch_buffers():
376
            if b.number == number:
377 5
                return b
378
        raise KeyError(number)
379 5
380
    def __contains__(self, b):
381 5
        """Return whether Buffer `b` is a known valid buffer."""
382
        return isinstance(b, Buffer) and b.valid
383 5
384
    def __iter__(self):
385
        """Return an iterator over the list of buffers."""
386 5
        return iter(self._fetch_buffers())
387
388
389
class CompatibilitySession(object):
390 5
391 5
    """Helper class for API compatibility."""
392
393
    def __init__(self, nvim):
394 5
        self.threadsafe_call = nvim.async_call
395
396
397
class Current(object):
398 5
399 5
    """Helper class for emulating vim.current from python-vim."""
400 5
401
    def __init__(self, session):
402 5
        self._session = session
403
        self.range = None
404 5
405
    @property
406 5
    def line(self):
407
        return self._session.request('nvim_get_current_line')
408 5
409
    @line.setter
410 5
    def line(self, line):
411
        return self._session.request('nvim_set_current_line', line)
412 5
413
    @property
414 5
    def buffer(self):
415
        return self._session.request('nvim_get_current_buf')
416 5
417
    @buffer.setter
418 5
    def buffer(self, buffer):
419
        return self._session.request('nvim_set_current_buf', buffer)
420 5
421
    @property
422 5
    def window(self):
423
        return self._session.request('nvim_get_current_win')
424 5
425
    @window.setter
426 5
    def window(self, window):
427
        return self._session.request('nvim_set_current_win', window)
428 5
429
    @property
430 5
    def tabpage(self):
431
        return self._session.request('nvim_get_current_tabpage')
432 5
433
    @tabpage.setter
434
    def tabpage(self, tabpage):
435 5
        return self._session.request('nvim_set_current_tabpage', tabpage)
436
437
438
class Funcs(object):
439 5
440 5
    """Helper class for functional vimscript interface."""
441
442 5
    def __init__(self, nvim):
443 5
        self._nvim = nvim
444
445
    def __getattr__(self, name):
446 5
        return functools.partial(self._nvim.call, name)
447 5
448
449
class NvimError(Exception):
450
    pass
451