Completed
Pull Request — master (#223)
by Björn
02:49
created

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