Completed
Pull Request — master (#179)
by Björn
02:26
created

neovim.api.Nvim   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Test Coverage

Coverage 78.86%
Metric Value
wmc 48
dl 0
loc 281
ccs 97
cts 123
cp 0.7886
rs 8.4864

34 Methods

Rating   Name   Duplication   Size   Complexity  
A from_session() 0 22 3
A run_loop() 0 16 4
A next_message() 0 5 2
A filter_notification_cb() 0 2 1
A _from_nvim() 0 7 3
A from_nvim() 0 5 1
A _to_nvim() 0 4 2
A __init__() 0 19 1
A with_decodehook() 0 4 1
A request() 0 5 1
A stop_loop() 0 3 1
A filter_request_cb() 0 4 1
A foreach_rtp() 0 14 4
A err_write() 0 3 1
A new_highlight_source() 0 3 1
A feedkeys() 0 10 1
A async_call() 0 22 3
A list_runtime_paths() 0 3 1
A handler() 0 9 2
A eval() 0 3 1
A ui_detach() 0 3 1
A out_write() 0 3 1
A ui_attach() 0 7 1
A quit() 0 13 2
A unsubscribe() 0 3 1
A replace_termcodes() 0 16 1
A subscribe() 0 3 1
A command_output() 0 3 1
A call() 0 7 3
A input() 0 9 1
A strwidth() 0 6 1
A chdir() 0 4 1
A ui_try_resize() 0 6 1
A command() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like neovim.api.Nvim often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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