Completed
Pull Request — master (#303)
by Björn
22:48
created

Nvim.__exit__()   A

Complexity

Conditions 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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