Completed
Pull Request — master (#209)
by
unknown
15:09 queued 13:40
created

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