Completed
Pull Request — master (#193)
by Björn
02:36
created

neovim.api.CompatibilitySession   A

Complexity

Total Complexity 1

Size/Duplication

Total Lines 6
Duplicated Lines 0 %

Test Coverage

Coverage 100%
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
wmc 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A __init__() 0 2 1
1
"""Main Nvim interface."""
2 6
import functools
3 6
import os
4 6
import sys
5
6 6
from traceback import format_exc, 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
17
18 6
__all__ = ('Nvim')
19
20
21 6
os_chdir = os.chdir
22
23
24 6
class Nvim(object):
25
26
    """Class that represents a remote Nvim instance.
27
28
    This class is main entry point to Nvim remote API, it is a wrapper
29
    around Session instances.
30
31
    The constructor of this class must not be called directly. Instead, the
32
    `from_session` class method should be used to create the first instance
33
    from a raw `Session` instance.
34
35
    Subsequent instances for the same session can be created by calling the
36
    `with_decode` instance method to change the decoding behavior or
37
    `SubClass.from_nvim(nvim)` where `SubClass` is a subclass of `Nvim`, which
38
    is useful for having multiple `Nvim` objects that behave differently
39
    without one affecting the other.
40
    """
41
42 6
    @classmethod
43
    def from_session(cls, session):
44
        """Create a new Nvim instance for a Session instance.
45
46
        This method must be called to create the first Nvim instance, since it
47
        queries Nvim metadata for type information and sets a SessionHook for
48
        creating specialized objects from Nvim remote handles.
49
        """
50 6
        session.error_wrapper = lambda e: NvimError(e[1])
51 6
        channel_id, metadata = session.request(b'vim_get_api_info')
52
53 6
        if IS_PYTHON3:
54
            # decode all metadata strings for python3
55 3
            metadata = walk(decode_if_bytes, metadata)
56
57 6
        types = {
58
            metadata['types']['Buffer']['id']: Buffer,
59
            metadata['types']['Window']['id']: Window,
60
            metadata['types']['Tabpage']['id']: Tabpage,
61
        }
62
63 6
        return cls(session, channel_id, metadata, types)
64
65 6
    @classmethod
66
    def from_nvim(cls, nvim):
67
        """Create a new Nvim instance from an existing instance."""
68
        return cls(nvim._session, nvim.channel_id, nvim.metadata,
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _session was declared protected and should not be accessed from this context.

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

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

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

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
69
                   nvim.types, nvim._decode, nvim._err_cb)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _decode was declared protected and should not be accessed from this context.

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

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

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

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
Coding Style Best Practice introduced by
It seems like _err_cb was declared protected and should not be accessed from this context.

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

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

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

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
70
71 6
    def __init__(self, session, channel_id, metadata, types,
72
                 decode=False, err_cb=None):
73
        """Initialize a new Nvim instance. This method is module-private."""
74 6
        self._session = session
75 6
        self.channel_id = channel_id
76 6
        self.metadata = metadata
77 6
        self.types = types
78 6
        self.api = RemoteApi(self, 'vim_')
79 6
        self.vars = RemoteMap(self, 'vim_get_var', 'vim_set_var')
80 6
        self.vvars = RemoteMap(self, 'vim_get_vvar', None)
81 6
        self.options = RemoteMap(self, 'vim_get_option', 'vim_set_option')
82 6
        self.buffers = RemoteSequence(self, 'vim_get_buffers')
83 6
        self.windows = RemoteSequence(self, 'vim_get_windows')
84 6
        self.tabpages = RemoteSequence(self, 'vim_get_tabpages')
85 6
        self.current = Current(self)
86 6
        self.session = CompatibilitySession(self)
87 6
        self.funcs = Funcs(self)
88 6
        self.error = NvimError
89 6
        self._decode = decode
90 6
        self._err_cb = err_cb
91
92 6
    def _from_nvim(self, obj, decode=None):
93 6
        if decode is None:
94 6
            decode = self._decode
95 6
        if type(obj) is ExtType:
96 6
            cls = self.types[obj.code]
97 6
            return cls(self, (obj.code, obj.data))
98 6
        if decode:
99 3
            obj = decode_if_bytes(obj, decode)
100 6
        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 6
        if isinstance(obj, Remote):
104 6
            return ExtType(*obj.code_data)
105 6
        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 6
        decode = kwargs.pop('decode', self._decode)
128 6
        args = walk(self._to_nvim, args)
129 6
        res = self._session.request(name, *args, **kwargs)
130 6
        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 6
        msg = self._session.next_message()
139 6
        if msg:
140 6
            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 6
        def filter_request_cb(name, args):
150 6
            args = walk(self._from_nvim, args)
151 6
            result = request_cb(self._from_nvim(name), args)
152 6
            return walk(self._to_nvim, result)
153
154 6
        def filter_notification_cb(name, args):
155
            notification_cb(self._from_nvim(name), walk(self._from_nvim, args))
156
157 6
        if err_cb is None:
158 6
            err_cb = sys.stderr.write
159 6
        self._err_cb = err_cb
160
161 6
        self._session.run(filter_request_cb, filter_notification_cb, setup_cb)
162
163 6
    def stop_loop(self):
164
        """Stop the event loop being started with `run_loop`."""
165 6
        self._session.stop()
166
167 6
    def with_decode(self, decode=True):
168
        """Initialize a new Nvim instance."""
169 6
        return Nvim(self._session, self.channel_id,
170
                    self.metadata, self.types, decode, self._err_cb)
171
172 6
    def ui_attach(self, width, height, rgb):
173
        """Register as a remote UI.
174
175
        After this method is called, the client will receive redraw
176
        notifications.
177
        """
178
        return self.request('ui_attach', width, height, rgb)
179
180 6
    def ui_detach(self):
181
        """Unregister as a remote UI."""
182
        return self.request('ui_detach')
183
184 6
    def ui_try_resize(self, width, height):
185
        """Notify nvim that the client window has resized.
186
187
        If possible, nvim will send a redraw request to resize.
188
        """
189
        return self.request('ui_try_resize', width, height)
190
191 6
    def subscribe(self, event):
192
        """Subscribe to a Nvim event."""
193 6
        return self.request('vim_subscribe', event)
194
195 6
    def unsubscribe(self, event):
196
        """Unsubscribe to a Nvim event."""
197 6
        return self.request('vim_unsubscribe', event)
198
199 6
    def command(self, string, **kwargs):
200
        """Execute a single ex command."""
201 6
        return self.request('vim_command', string, **kwargs)
202
203 6
    def command_output(self, string):
204
        """Execute a single ex command and return the output."""
205
        return self.request('vim_command_output', string)
206
207 6
    def eval(self, string, **kwargs):
208
        """Evaluate a vimscript expression."""
209 6
        return self.request('vim_eval', string, **kwargs)
210
211 6
    def call(self, name, *args, **kwargs):
212
        """Call a vimscript function."""
213 6
        return self.request('vim_call_function', name, args, **kwargs)
214
215 6
    def strwidth(self, string):
216
        """Return the number of display cells `string` occupies.
217
218
        Tab is counted as one cell.
219
        """
220 6
        return self.request('vim_strwidth', string)
221
222 6
    def list_runtime_paths(self):
223
        """Return a list of paths contained in the 'runtimepath' option."""
224
        return self.request('vim_list_runtime_paths')
225
226 6
    def foreach_rtp(self, cb):
227
        """Invoke `cb` for each path in 'runtimepath'.
228
229
        Call the given callable for each path in 'runtimepath' until either
230
        callable returns something but None, the exception is raised or there
231
        are no longer paths. If stopped in case callable returned non-None,
232
        vim.foreach_rtp function returns the value returned by callable.
233
        """
234
        for path in self.request('vim_list_runtime_paths'):
235 1
            try:
236 1
                if cb(path) is not None:
237
                    break
238
            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...
239
                break
240
241 6
    def chdir(self, dir_path):
242
        """Run os.chdir, then all appropriate vim stuff."""
243 6
        os_chdir(dir_path)
244 6
        return self.request('vim_change_directory', dir_path)
245
246 6
    def feedkeys(self, keys, options='', escape_csi=True):
247
        """Push `keys` to Nvim user input buffer.
248
249
        Options can be a string with the following character flags:
250
        - 'm': Remap keys. This is default.
251
        - 'n': Do not remap keys.
252
        - 't': Handle keys as if typed; otherwise they are handled as if coming
253
               from a mapping. This matters for undo, opening folds, etc.
254
        """
255
        return self.request('vim_feedkeys', keys, options, escape_csi)
256
257 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...
258
        """Push `bytes` to Nvim low level input buffer.
259
260
        Unlike `feedkeys()`, this uses the lowest level input buffer and the
261
        call is not deferred. It returns the number of bytes actually
262
        written(which can be less than what was requested if the buffer is
263
        full).
264
        """
265 6
        return self.request('vim_input', bytes)
266
267 6
    def replace_termcodes(self, string, from_part=False, do_lt=True,
268
                          special=True):
269
        r"""Replace any terminal code strings by byte sequences.
270
271
        The returned sequences are Nvim's internal representation of keys,
272
        for example:
273
274
        <esc> -> '\x1b'
275
        <cr>  -> '\r'
276
        <c-l> -> '\x0c'
277
        <up>  -> '\x80ku'
278
279
        The returned sequences can be used as input to `feedkeys`.
280
        """
281
        return self.request('vim_replace_termcodes', string,
282
                            from_part, do_lt, special)
283
284 6
    def out_write(self, msg):
285
        """Print `msg` as a normal message."""
286
        return self.request('vim_out_write', msg)
287
288 6
    def err_write(self, msg, **kwargs):
289
        """Print `msg` as an error message."""
290
        return self.request('vim_err_write', msg, **kwargs)
291
292 6
    def quit(self, quit_command='qa!'):
293
        """Send a quit command to Nvim.
294
295
        By default, the quit command is 'qa!' which will make Nvim quit without
296
        saving anything.
297
        """
298
        try:
299
            self.command(quit_command)
300 1
        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...
301
            # sending a quit command will raise an IOError because the
302
            # connection is closed before a response is received. Safe to
303
            # ignore it.
304
            pass
305
306 6
    def new_highlight_source(self):
307
        """Return new src_id for use with Buffer.add_highlight."""
308 4
        return self.current.buffer.add_highlight("", 0, src_id=0)
309
310 6
    def async_call(self, fn, *args, **kwargs):
311
        """Schedule `fn` to be called by the event loop soon.
312
313
        This function is thread-safe, and is the only way code not
314
        on the main thread could interact with nvim api objects.
315
316
        This function can also be called in a synchronous
317
        event handler, just before it returns, to defer execution
318
        that shouldn't block neovim.
319
        """
320 6
        call_point = ''.join(format_stack(None, 5)[:-1])
321
322 6
        def handler():
323 6
            try:
324 6
                fn(*args, **kwargs)
325
            except Exception as err:
326
                msg = ("error caught while executing async callback:\n"
327
                       "{0!r}\n{1}\n \nthe call was requested at\n{2}"
328
                       .format(err, format_exc(5), call_point))
329
                self._err_cb(msg)
330
                raise
331 6
        self._session.threadsafe_call(handler)
332
333
334 6
class CompatibilitySession(object):
335
336
    """Helper class for API compatibility."""
337
338 6
    def __init__(self, nvim):
339 6
        self.threadsafe_call = nvim.async_call
340
341
342 6
class Current(object):
343
344
    """Helper class for emulating vim.current from python-vim."""
345
346 6
    def __init__(self, session):
347 6
        self._session = session
348 6
        self.range = None
349
350 6
    @property
351
    def line(self):
352 6
        return self._session.request('vim_get_current_line')
353
354 6
    @line.setter
355
    def line(self, line):
356 6
        return self._session.request('vim_set_current_line', line)
357
358 6
    @property
359
    def buffer(self):
360 6
        return self._session.request('vim_get_current_buffer')
361
362 6
    @buffer.setter
363
    def buffer(self, buffer):
364 6
        return self._session.request('vim_set_current_buffer', buffer)
365
366 6
    @property
367
    def window(self):
368 6
        return self._session.request('vim_get_current_window')
369
370 6
    @window.setter
371
    def window(self, window):
372 6
        return self._session.request('vim_set_current_window', window)
373
374 6
    @property
375
    def tabpage(self):
376 6
        return self._session.request('vim_get_current_tabpage')
377
378 6
    @tabpage.setter
379
    def tabpage(self, tabpage):
380 6
        return self._session.request('vim_set_current_tabpage', tabpage)
381
382
383 6
class Funcs(object):
384
385
    """Helper class for functional vimscript interface."""
386
387 6
    def __init__(self, nvim):
388 6
        self._nvim = nvim
389
390 6
    def __getattr__(self, name):
391 6
        return functools.partial(self._nvim.call, name)
392
393
394 6
class NvimError(Exception):
395
    pass
396