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

neovim.api.Nvim.command()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

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