Completed
Pull Request — master (#179)
by Björn
62:29 queued 39:48
created

neovim.api.Nvim   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Test Coverage

Coverage 72.48%
Metric Value
dl 0
loc 276
ccs 79
cts 109
cp 0.7248
rs 8.4864
wmc 48

35 Methods

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