Completed
Pull Request — master (#189)
by Björn
22:01
created

neovim.api.Nvim.request()   B

Complexity

Conditions 1

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

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