Completed
Pull Request — master (#255)
by Björn
21:29
created

Nvim._thread_invalid()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

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