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

neovim.api.Nvim.stop_loop()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

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