Completed
Push — master ( 13f763...d9aed9 )
by Björn
30:55 queued 05:49
created

LuaFuncs.__init__()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
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
lua_module = """
23
local a = vim.api
24 5
local function update_highlights(buf, src_id, hls, clear_first, clear_end)
25
  if clear_first ~= nil then
26
      a.nvim_buf_clear_highlight(buf, src_id, clear_first, clear_end)
27
  end
28
  for _,hl in pairs(hls) do
29
    local group, line, col_start, col_end = unpack(hl)
30
    if col_start == nil then
31
      col_start = 0
32
    end
33
    if col_end == nil then
34
      col_end = -1
35
    end
36
    a.nvim_buf_add_highlight(buf, src_id, group, line, col_start, col_end)
37
  end
38
end
39
40
local chid = ...
41
local mod = {update_highlights=update_highlights}
42 5
_G["_pynvim_"..chid] = mod
43
"""
44
45
46
class Nvim(object):
47
48
    """Class that represents a remote Nvim instance.
49
50 5
    This class is main entry point to Nvim remote API, it is a wrapper
51 5
    around Session instances.
52
53 5
    The constructor of this class must not be called directly. Instead, the
54
    `from_session` class method should be used to create the first instance
55 5
    from a raw `Session` instance.
56
57 5
    Subsequent instances for the same session can be created by calling the
58
    `with_decode` instance method to change the decoding behavior or
59
    `SubClass.from_nvim(nvim)` where `SubClass` is a subclass of `Nvim`, which
60
    is useful for having multiple `Nvim` objects that behave differently
61
    without one affecting the other.
62
63 5
    When this library is used on python3.4+, asyncio event loop is guaranteed
64
    to be used. It is available as the "loop" attribute of this class. Note
65 5
    that asyncio callbacks cannot make blocking requests, which includes
66
    accessing state-dependent attributes. They should instead schedule another
67
    callback using nvim.async_call, which will not have this restriction.
68
    """
69
70
    @classmethod
71 5
    def from_session(cls, session):
72
        """Create a new Nvim instance for a Session instance.
73
74 5
        This method must be called to create the first Nvim instance, since it
75 5
        queries Nvim metadata for type information and sets a SessionHook for
76 5
        creating specialized objects from Nvim remote handles.
77 5
        """
78 5
        session.error_wrapper = lambda e: NvimError(e[1])
79 5
        channel_id, metadata = session.request(b'vim_get_api_info')
80 5
81 5
        if IS_PYTHON3:
82 5
            # decode all metadata strings for python3
83 5
            metadata = walk(decode_if_bytes, metadata)
84 5
85 5
        types = {
86 5
            metadata['types']['Buffer']['id']: Buffer,
87 5
            metadata['types']['Window']['id']: Window,
88 5
            metadata['types']['Tabpage']['id']: Tabpage,
89 5
        }
90 5
91 5
        return cls(session, channel_id, metadata, types)
92 5
93
    @classmethod
94 5
    def from_nvim(cls, nvim):
95 5
        """Create a new Nvim instance from an existing instance."""
96 5
        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...
97 5
                   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...
98 5
99 5
    def __init__(self, session, channel_id, metadata, types,
100 5
                 decode=False, err_cb=None):
101 3
        """Initialize a new Nvim instance. This method is module-private."""
102 5
        self._session = session
103
        self.channel_id = channel_id
104 5
        self.metadata = metadata
105 5
        version = metadata.get("version", {"api_level": 0})
106 5
        self.version = Version(**version)
107 5
        self.types = types
108
        self.api = RemoteApi(self, 'nvim_')
109 5
        self.vars = RemoteMap(self, 'nvim_get_var', 'nvim_set_var')
110
        self.vvars = RemoteMap(self, 'nvim_get_vvar', None)
111
        self.options = RemoteMap(self, 'nvim_get_option', 'nvim_set_option')
112
        self.buffers = Buffers(self)
113
        self.windows = RemoteSequence(self, 'nvim_list_wins')
114
        self.tabpages = RemoteSequence(self, 'nvim_list_tabpages')
115
        self.current = Current(self)
116
        self.session = CompatibilitySession(self)
117
        self.funcs = Funcs(self)
118
        self.lua = LuaFuncs(self)
119
        self.error = NvimError
120
        self._decode = decode
121
        self._err_cb = err_cb
122
123
        # only on python3.4+ we expose asyncio
124
        if IS_PYTHON3 and os.name != 'nt':
125
            self.loop = self._session.loop._loop
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _loop was declared protected and should not be accessed from this context.

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

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

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

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