Completed
Pull Request — master (#179)
by Björn
24:47
created

neovim.api.Nvim   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Test Coverage

Coverage 72.72%
Metric Value
dl 0
loc 278
ccs 80
cts 110
cp 0.7272
rs 8.4864
wmc 48

35 Methods

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