Completed
Push — master ( f8d0c1...a5e3d2 )
by Björn
20:03 queued 18:32
created

Nvim.request()   B

Complexity

Conditions 1

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 24
ccs 5
cts 5
cp 1
crap 1
rs 8.9713
c 0
b 0
f 0
1
"""Main Nvim interface."""
2 6
import functools
3 6
import os
4 6
import sys
5
6 6
from traceback import format_stack
7
8 6
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...
9
10 6
from .buffer import Buffer
11 6
from .common import (Remote, RemoteApi, RemoteMap, RemoteSequence,
12
                     decode_if_bytes, walk)
13 6
from .tabpage import Tabpage
14 6
from .window import Window
15 6
from ..compat import IS_PYTHON3
16 6
from ..util import Version, format_exc_skip
17
18 6
__all__ = ('Nvim')
19
20
21 6
os_chdir = os.chdir
22
23
24 6
class Nvim(object):
25
26
    """Class that represents a remote Nvim instance.
27
28
    This class is main entry point to Nvim remote API, it is a wrapper
29
    around Session instances.
30
31
    The constructor of this class must not be called directly. Instead, the
32
    `from_session` class method should be used to create the first instance
33
    from a raw `Session` instance.
34
35
    Subsequent instances for the same session can be created by calling the
36
    `with_decode` instance method to change the decoding behavior or
37
    `SubClass.from_nvim(nvim)` where `SubClass` is a subclass of `Nvim`, which
38
    is useful for having multiple `Nvim` objects that behave differently
39
    without one affecting the other.
40
    """
41
42 6
    @classmethod
43
    def from_session(cls, session):
44
        """Create a new Nvim instance for a Session instance.
45
46
        This method must be called to create the first Nvim instance, since it
47
        queries Nvim metadata for type information and sets a SessionHook for
48
        creating specialized objects from Nvim remote handles.
49
        """
50 6
        session.error_wrapper = lambda e: NvimError(e[1])
51 6
        channel_id, metadata = session.request(b'vim_get_api_info')
52
53 6
        if IS_PYTHON3:
54
            # decode all metadata strings for python3
55 6
            metadata = walk(decode_if_bytes, metadata)
56
57 6
        types = {
58
            metadata['types']['Buffer']['id']: Buffer,
59
            metadata['types']['Window']['id']: Window,
60
            metadata['types']['Tabpage']['id']: Tabpage,
61
        }
62
63 6
        return cls(session, channel_id, metadata, types)
64
65 6
    @classmethod
66
    def from_nvim(cls, nvim):
67
        """Create a new Nvim instance from an existing instance."""
68
        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...
69
                   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...
70
71 6
    def __init__(self, session, channel_id, metadata, types,
72
                 decode=False, err_cb=None):
73
        """Initialize a new Nvim instance. This method is module-private."""
74 6
        self._session = session
75 6
        self.channel_id = channel_id
76 6
        self.metadata = metadata
77 6
        version = metadata.get("version", {"api_level": 0})
78 6
        self.version = Version(**version)
79 6
        self.types = types
80 6
        self.api = RemoteApi(self, 'nvim_')
81 6
        self.vars = RemoteMap(self, 'nvim_get_var', 'nvim_set_var')
82 6
        self.vvars = RemoteMap(self, 'vim_get_vvar', None)
83 6
        self.options = RemoteMap(self, 'nvim_get_option', 'nvim_set_option')
84 6
        self.buffers = Buffers(self)
85 6
        self.windows = RemoteSequence(self, 'nvim_list_wins')
86 6
        self.tabpages = RemoteSequence(self, 'nvim_list_tabpages')
87 6
        self.current = Current(self)
88 6
        self.session = CompatibilitySession(self)
89 6
        self.funcs = Funcs(self)
90 6
        self.error = NvimError
91 6
        self._decode = decode
92 6
        self._err_cb = err_cb
93
94 6
    def _from_nvim(self, obj, decode=None):
95 6
        if decode is None:
96 6
            decode = self._decode
97 6
        if type(obj) is ExtType:
98 6
            cls = self.types[obj.code]
99 6
            return cls(self, (obj.code, obj.data))
100 6
        if decode:
101 3
            obj = decode_if_bytes(obj, decode)
102 6
        return obj
103
104 6
    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...
105 6
        if isinstance(obj, Remote):
106 6
            return ExtType(*obj.code_data)
107 6
        return obj
108
109 6
    def request(self, name, *args, **kwargs):
110
        r"""Send an API request or notification to nvim.
111
112
        It is rarely needed to call this function directly, as most API
113
        functions have python wrapper functions. The `api` object can
114
        be also be used to call API functions as methods:
115
116
            vim.api.err_write('ERROR\n', async=True)
117
            vim.current.buffer.api.get_mark('.')
118
119
        is equivalent to
120
121
            vim.request('nvim_err_write', 'ERROR\n', async=True)
122
            vim.request('nvim_buf_get_mark', vim.current.buffer, '.')
123
124
125
        Normally a blocking request will be sent.  If the `async` flag is
126
        present and True, a asynchronous notification is sent instead. This
127
        will never block, and the return value or error is ignored.
128
        """
129 6
        decode = kwargs.pop('decode', self._decode)
130 6
        args = walk(self._to_nvim, args)
131 6
        res = self._session.request(name, *args, **kwargs)
132 6
        return walk(self._from_nvim, res, decode=decode)
133
134 6
    def next_message(self):
135
        """Block until a message(request or notification) is available.
136
137
        If any messages were previously enqueued, return the first in queue.
138
        If not, run the event loop until one is received.
139
        """
140 6
        msg = self._session.next_message()
141 6
        if msg:
142 6
            return walk(self._from_nvim, msg)
143
144 6
    def run_loop(self, request_cb, notification_cb,
145
                 setup_cb=None, err_cb=None):
146
        """Run the event loop to receive requests and notifications from Nvim.
147
148
        This should not be called from a plugin running in the host, which
149
        already runs the loop and dispatches events to plugins.
150
        """
151 6
        if err_cb is None:
152 6
            err_cb = sys.stderr.write
153 6
        self._err_cb = err_cb
154
155 6
        def filter_request_cb(name, args):
156 6
            name = self._from_nvim(name)
157 6
            args = walk(self._from_nvim, args)
158 6
            try:
159 6
                result = request_cb(name, args)
160
            except Exception:
161
                msg = ("error caught in request handler '{} {}'\n{}\n\n"
162
                       .format(name, args, format_exc_skip(1, 5)))
163
                self._err_cb(msg)
164
                raise
165 6
            return walk(self._to_nvim, result)
166
167 6
        def filter_notification_cb(name, args):
168
            name = self._from_nvim(name)
169
            args = walk(self._from_nvim, args)
170
            try:
171
                notification_cb(name, args)
172
            except Exception:
173
                msg = ("error caught in notification handler '{} {}'\n{}\n\n"
174
                       .format(name, args, format_exc_skip(1, 5)))
175
                self._err_cb(msg)
176
                raise
177
178 6
        self._session.run(filter_request_cb, filter_notification_cb, setup_cb)
179
180 6
    def stop_loop(self):
181
        """Stop the event loop being started with `run_loop`."""
182 6
        self._session.stop()
183
184 6
    def with_decode(self, decode=True):
185
        """Initialize a new Nvim instance."""
186 6
        return Nvim(self._session, self.channel_id,
187
                    self.metadata, self.types, decode, self._err_cb)
188
189 6
    def ui_attach(self, width, height, rgb):
190
        """Register as a remote UI.
191
192
        After this method is called, the client will receive redraw
193
        notifications.
194
        """
195
        return self.request('ui_attach', width, height, rgb)
196
197 6
    def ui_detach(self):
198
        """Unregister as a remote UI."""
199
        return self.request('ui_detach')
200
201 6
    def ui_try_resize(self, width, height):
202
        """Notify nvim that the client window has resized.
203
204
        If possible, nvim will send a redraw request to resize.
205
        """
206
        return self.request('ui_try_resize', width, height)
207
208 6
    def subscribe(self, event):
209
        """Subscribe to a Nvim event."""
210 6
        return self.request('nvim_subscribe', event)
211
212 6
    def unsubscribe(self, event):
213
        """Unsubscribe to a Nvim event."""
214 6
        return self.request('nvim_unsubscribe', event)
215
216 6
    def command(self, string, **kwargs):
217
        """Execute a single ex command."""
218 6
        return self.request('nvim_command', string, **kwargs)
219
220 6
    def command_output(self, string):
221
        """Execute a single ex command and return the output."""
222
        return self.request('nvim_command_output', string)
223
224 6
    def eval(self, string, **kwargs):
225
        """Evaluate a vimscript expression."""
226 6
        return self.request('nvim_eval', string, **kwargs)
227
228 6
    def call(self, name, *args, **kwargs):
229
        """Call a vimscript function."""
230 6
        return self.request('nvim_call_function', name, args, **kwargs)
231
232 6
    def strwidth(self, string):
233
        """Return the number of display cells `string` occupies.
234
235
        Tab is counted as one cell.
236
        """
237 6
        return self.request('nvim_strwidth', string)
238
239 6
    def list_runtime_paths(self):
240
        """Return a list of paths contained in the 'runtimepath' option."""
241
        return self.request('nvim_list_runtime_paths')
242
243 6
    def foreach_rtp(self, cb):
244
        """Invoke `cb` for each path in 'runtimepath'.
245
246
        Call the given callable for each path in 'runtimepath' until either
247
        callable returns something but None, the exception is raised or there
248
        are no longer paths. If stopped in case callable returned non-None,
249
        vim.foreach_rtp function returns the value returned by callable.
250
        """
251
        for path in self.request('nvim_list_runtime_paths'):
252
            try:
253
                if cb(path) is not None:
254
                    break
255
            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...
256
                break
257
258 6
    def chdir(self, dir_path):
259
        """Run os.chdir, then all appropriate vim stuff."""
260 6
        os_chdir(dir_path)
261 6
        return self.request('nvim_set_current_dir', dir_path)
262
263 6
    def feedkeys(self, keys, options='', escape_csi=True):
264
        """Push `keys` to Nvim user input buffer.
265
266
        Options can be a string with the following character flags:
267
        - 'm': Remap keys. This is default.
268
        - 'n': Do not remap keys.
269
        - 't': Handle keys as if typed; otherwise they are handled as if coming
270
               from a mapping. This matters for undo, opening folds, etc.
271
        """
272
        return self.request('nvim_feedkeys', keys, options, escape_csi)
273
274 6
    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...
275
        """Push `bytes` to Nvim low level input buffer.
276
277
        Unlike `feedkeys()`, this uses the lowest level input buffer and the
278
        call is not deferred. It returns the number of bytes actually
279
        written(which can be less than what was requested if the buffer is
280
        full).
281
        """
282 6
        return self.request('nvim_input', bytes)
283
284 6
    def replace_termcodes(self, string, from_part=False, do_lt=True,
285
                          special=True):
286
        r"""Replace any terminal code strings by byte sequences.
287
288
        The returned sequences are Nvim's internal representation of keys,
289
        for example:
290
291
        <esc> -> '\x1b'
292
        <cr>  -> '\r'
293
        <c-l> -> '\x0c'
294
        <up>  -> '\x80ku'
295
296
        The returned sequences can be used as input to `feedkeys`.
297
        """
298
        return self.request('vim_replace_termcodes', string,
299
                            from_part, do_lt, special)
300
301 6
    def out_write(self, msg):
302
        """Print `msg` as a normal message."""
303
        return self.request('nvim_out_write', msg)
304
305 6
    def err_write(self, msg, **kwargs):
306
        """Print `msg` as an error message."""
307 3
        return self.request('nvim_err_write', msg, **kwargs)
308
309 6
    def quit(self, quit_command='qa!'):
310
        """Send a quit command to Nvim.
311
312
        By default, the quit command is 'qa!' which will make Nvim quit without
313
        saving anything.
314
        """
315
        try:
316
            self.command(quit_command)
317
        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...
318
            # sending a quit command will raise an IOError because the
319
            # connection is closed before a response is received. Safe to
320
            # ignore it.
321
            pass
322
323 6
    def new_highlight_source(self):
324
        """Return new src_id for use with Buffer.add_highlight."""
325
        return self.current.buffer.add_highlight("", 0, src_id=0)
326
327 6
    def async_call(self, fn, *args, **kwargs):
328
        """Schedule `fn` to be called by the event loop soon.
329
330
        This function is thread-safe, and is the only way code not
331
        on the main thread could interact with nvim api objects.
332
333
        This function can also be called in a synchronous
334
        event handler, just before it returns, to defer execution
335
        that shouldn't block neovim.
336
        """
337 6
        call_point = ''.join(format_stack(None, 5)[:-1])
338
339 6
        def handler():
340 6
            try:
341 6
                fn(*args, **kwargs)
342
            except Exception as err:
343 1
                msg = ("error caught while executing async callback:\n"
344
                       "{0!r}\n{1}\n \nthe call was requested at\n{2}"
345
                       .format(err, format_exc_skip(1, 5), call_point))
346 1
                self._err_cb(msg)
347
                raise
348 6
        self._session.threadsafe_call(handler)
349
350
351 6
class Buffers(object):
352
353
    """Remote NVim buffers.
354
355
    Currently the interface for interacting with remote NVim buffers is the
356
    `vim_get_buffers` msgpack-rpc function. Most methods fetch the list of
357
    buffers from NVim.
358
359
    Conforms to *python-buffers*.
360
    """
361
362 6
    def __init__(self, nvim):
363
        """Initialize a Buffers object with Nvim object `nvim`."""
364 6
        self._fetch_buffers = nvim.api.list_bufs
365
366 6
    def __len__(self):
367
        """Return the count of buffers."""
368 6
        return len(self._fetch_buffers())
369
370 6
    def __getitem__(self, number):
371
        """Return the Buffer object matching buffer number `number`."""
372 6
        for b in self._fetch_buffers():
373 6
            if b.number == number:
374 6
                return b
375
        raise KeyError(number)
376
377 6
    def __contains__(self, b):
378
        """Return whether Buffer `b` is a known valid buffer."""
379 6
        return isinstance(b, Buffer) and b.valid
380
381 6
    def __iter__(self):
382
        """Return an iterator over the list of buffers."""
383 6
        return iter(self._fetch_buffers())
384
385
386 6
class CompatibilitySession(object):
387
388
    """Helper class for API compatibility."""
389
390 6
    def __init__(self, nvim):
391 6
        self.threadsafe_call = nvim.async_call
392
393
394 6
class Current(object):
395
396
    """Helper class for emulating vim.current from python-vim."""
397
398 6
    def __init__(self, session):
399 6
        self._session = session
400 6
        self.range = None
401
402 6
    @property
403
    def line(self):
404 6
        return self._session.request('nvim_get_current_line')
405
406 6
    @line.setter
407
    def line(self, line):
408 6
        return self._session.request('nvim_set_current_line', line)
409
410 6
    @property
411
    def buffer(self):
412 6
        return self._session.request('nvim_get_current_buf')
413
414 6
    @buffer.setter
415
    def buffer(self, buffer):
416 6
        return self._session.request('nvim_set_current_buf', buffer)
417
418 6
    @property
419
    def window(self):
420 6
        return self._session.request('nvim_get_current_win')
421
422 6
    @window.setter
423
    def window(self, window):
424 6
        return self._session.request('nvim_set_current_win', window)
425
426 6
    @property
427
    def tabpage(self):
428 6
        return self._session.request('nvim_get_current_tabpage')
429
430 6
    @tabpage.setter
431
    def tabpage(self, tabpage):
432 6
        return self._session.request('nvim_set_current_tabpage', tabpage)
433
434
435 6
class Funcs(object):
436
437
    """Helper class for functional vimscript interface."""
438
439 6
    def __init__(self, nvim):
440 6
        self._nvim = nvim
441
442 6
    def __getattr__(self, name):
443 6
        return functools.partial(self._nvim.call, name)
444
445
446 6
class NvimError(Exception):
447
    pass
448