Completed
Pull Request — master (#340)
by Björn
21:46
created

Host._on_buf_changedtick()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
dl 0
loc 5
ccs 0
cts 0
cp 0
crap 6
rs 9.4285
c 1
b 0
f 0
1
"""Implements a Nvim host for python plugins."""
2 5
import imp
3 5
import inspect
4 5
import logging
5 5
import os
6 5
import os.path
7 5
import re
8 5
from functools import partial
9
from traceback import format_exc
10 5
11
from . import script_host
12 5
from ..api import decode_if_bytes, walk
13 5
from ..compat import IS_PYTHON3, find_module
14 5
from ..msgpack_rpc import ErrorResponse
15 5
from ..util import format_exc_skip
16 5
17
__all__ = ('Host')
18 5
19
logger = logging.getLogger(__name__)
20 5
error, debug, info, warn = (logger.error, logger.debug, logger.info,
21 5
                            logger.warning,)
22
23
24
class Host(object):
25 5
26
    """Nvim host for python plugins.
27
28
    Takes care of loading/unloading plugins and routing msgpack-rpc
29
    requests/notifications to the appropriate handlers.
30
    """
31
32
    def __init__(self, nvim):
33 5
        """Set handlers for plugin_load/plugin_unload."""
34
        self.nvim = nvim
35
        self.nvim._session.attached_buffers = {}
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _session 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...
36
        # TODO: delet this
37
        self.nvim._session.elog = []
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _session 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...
38
        self._specs = {}
39
        self._loaded = {}
40
        self._load_errors = {}
41
        self._notification_handlers = {
42
                'nvim_buf_lines_event': self._on_buf_lines,
43
                'nvim_buf_changedtick_event': self._on_buf_changedtick,
44
                'nvim_buf_detach_event': self._on_buf_detach,
45
            }
46
        self._request_handlers = {
47
            'poll': lambda: 'ok',
48
            'specs': self._on_specs_request,
49 5
            'shutdown': self.shutdown
50
        }
51
52 5
        # Decode per default for Python3
53
        self._decode_default = IS_PYTHON3
54
55
    def _on_async_err(self, msg):
56
        self.nvim.err_write(msg, async_=True)
57
58
    def start(self, plugins):
59 5
        """Start listening for msgpack-rpc requests and notifications."""
60
        self.nvim.run_loop(self._on_request,
61
                           self._on_notification,
62
                           lambda: self._load(plugins),
63
                           err_cb=self._on_async_err)
64 5
65
    def shutdown(self):
66
        """Shutdown the host."""
67
        self._unload()
68
        self.nvim.stop_loop()
69
70
    def _wrap_function(self, fn, sync, decode, nvim_bind, name, *args):
71
        if decode:
72
            args = walk(decode_if_bytes, args, decode)
73
        if nvim_bind is not None:
74
            args.insert(0, nvim_bind)
75
        try:
76
            return fn(*args)
77
        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...
78
            if sync:
79
                msg = ("error caught in request handler '{} {}':\n{}"
80
                       .format(name, args, format_exc_skip(1)))
81 5
                raise ErrorResponse(msg)
82
            else:
83
                msg = ("error caught in async handler '{} {}'\n{}\n"
84
                       .format(name, args, format_exc_skip(1)))
85
                self._on_async_err(msg + "\n")
86
87
    def _on_request(self, name, args):
88
        """Handle a msgpack-rpc request."""
89
        if IS_PYTHON3:
90
            name = decode_if_bytes(name)
91
        handler = self._request_handlers.get(name, None)
92
        if not handler:
93
            msg = self._missing_handler_error(name, 'request')
94
            error(msg)
95
            raise ErrorResponse(msg)
96 5
97
        debug('calling request handler for "%s", args: "%s"', name, args)
98
        rv = handler(*args)
99
        debug("request handler for '%s %s' returns: %s", name, args, rv)
100
        return rv
101
102
    def _on_notification(self, name, args):
103
        """Handle a msgpack-rpc notification."""
104
        if IS_PYTHON3:
105
            name = decode_if_bytes(name)
106
        self.nvim._session.elog.append([name]+args)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _session 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...
107
        handler = self._notification_handlers.get(name, None)
108
        if not handler:
109
            msg = self._missing_handler_error(name, 'notification')
110 5
            error(msg)
111
            self._on_async_err(msg + "\n")
112
            return
113
114
        debug('calling notification handler for "%s", args: "%s"', name, args)
115
        handler(*args)
116
117
    def _missing_handler_error(self, name, kind):
118
        msg = 'no {} handler registered for "{}"'.format(kind, name)
119 5
        pathmatch = re.match(r'(.+):[^:]+:[^:]+', name)
120
        if pathmatch:
121
            loader_error = self._load_errors.get(pathmatch.group(1))
122
            if loader_error is not None:
123
                msg = msg + "\n" + loader_error
124
        return msg
125
126
    def _load(self, plugins):
127
        for path in plugins:
128
            err = None
129
            if path in self._loaded:
130
                error('{} is already loaded'.format(path))
131
                continue
132
            try:
133
                if path == "script_host.py":
134
                    module = script_host
135
                else:
136
                    directory, name = os.path.split(os.path.splitext(path)[0])
137
                    file, pathname, descr = find_module(name, [directory])
138
                    module = imp.load_module(name, file, pathname, descr)
139
                handlers = []
140
                self._discover_classes(module, handlers, path)
141
                self._discover_functions(module, handlers, path)
142
                if not handlers:
143
                    error('{} exports no handlers'.format(path))
144
                    continue
145 5
                self._loaded[path] = {'handlers': handlers, 'module': module}
146
            except Exception as e:
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...
147
                err = ('Encountered {} loading plugin at {}: {}\n{}'
148
                       .format(type(e).__name__, path, e, format_exc(5)))
149
                error(err)
150
                self._load_errors[path] = err
151
152
    def _unload(self):
153
        for path, plugin in self._loaded.items():
0 ignored issues
show
Unused Code introduced by
The variable path seems to be unused.
Loading history...
154
            handlers = plugin['handlers']
155
            for handler in handlers:
156
                method_name = handler._nvim_rpc_method_name
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _nvim_rpc_method_name 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...
157
                if hasattr(handler, '_nvim_shutdown_hook'):
158
                    handler()
159 5
                elif handler._nvim_rpc_sync:
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _nvim_rpc_sync 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...
160
                    del self._request_handlers[method_name]
161
                else:
162
                    del self._notification_handlers[method_name]
163
        self._specs = {}
164
        self._loaded = {}
165
166
    def _discover_classes(self, module, handlers, plugin_path):
167 5
        for _, cls in inspect.getmembers(module, inspect.isclass):
168
            if getattr(cls, '_nvim_plugin', False):
169
                # create an instance of the plugin and pass the nvim object
170
                plugin = cls(self._configure_nvim_for(cls))
171
                # discover handlers in the plugin instance
172
                self._discover_functions(plugin, handlers, plugin_path)
173
174
    def _discover_functions(self, obj, handlers, plugin_path):
175
        def predicate(o):
176
            return hasattr(o, '_nvim_rpc_method_name')
177
178
        specs = []
179
        objdecode = getattr(obj, '_nvim_decode', self._decode_default)
180
        for _, fn in inspect.getmembers(obj, predicate):
181
            sync = fn._nvim_rpc_sync
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _nvim_rpc_sync 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...
182
            decode = getattr(fn, '_nvim_decode', objdecode)
183
            nvim_bind = None
184
            if fn._nvim_bind:
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _nvim_bind 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...
185
                nvim_bind = self._configure_nvim_for(fn)
186
187
            method = fn._nvim_rpc_method_name
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _nvim_rpc_method_name 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...
188
            if fn._nvim_prefix_plugin_path:
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _nvim_prefix_plugin_path 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...
189
                method = '{}:{}'.format(plugin_path, method)
190
191
            fn_wrapped = partial(self._wrap_function, fn,
192
                                 sync, decode, nvim_bind, method)
193
            self._copy_attributes(fn, fn_wrapped)
194
            # register in the rpc handler dict
195
            if sync:
196
                if method in self._request_handlers:
197
                    raise Exception(('Request handler for "{}" is ' +
198
                                    'already registered').format(method))
199
                self._request_handlers[method] = fn_wrapped
200
            else:
201
                if method in self._notification_handlers:
202
                    raise Exception(('Notification handler for "{}" is ' +
203
                                    'already registered').format(method))
204 5
                self._notification_handlers[method] = fn_wrapped
205
            if hasattr(fn, '_nvim_rpc_spec'):
206
                specs.append(fn._nvim_rpc_spec)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _nvim_rpc_spec 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...
207
            handlers.append(fn_wrapped)
208
        if specs:
209
            self._specs[plugin_path] = specs
210 5
211
    def _copy_attributes(self, fn, fn2):
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...
212
        # Copy _nvim_* attributes from the original function
213
        for attr in dir(fn):
214
            if attr.startswith('_nvim_'):
215
                setattr(fn2, attr, getattr(fn, attr))
216
217 5
    def _on_specs_request(self, path):
218
        if IS_PYTHON3:
219
            path = decode_if_bytes(path)
220
        if path in self._load_errors:
221
            self.nvim.out_write(self._load_errors[path] + '\n')
222
        return self._specs.get(path, 0)
223
224
    def _configure_nvim_for(self, obj):
225
        # Configure a nvim instance for obj (checks encoding configuration)
226
        nvim = self.nvim
227
        decode = getattr(obj, '_nvim_decode', self._decode_default)
228
        if decode:
229
            nvim = nvim.with_decode(decode)
230
        return nvim
231
232
    def _on_buf_lines(self, buf, changedtick, first, last, data, more):
233
        a = self.nvim._session.attached_buffers[buf.handle]
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _session 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...
234
        a.lines[first:last] = data
235
        a.changedtick = changedtick
236
        if more: return
237
        for cb in a.callbacks:
238
            cb(buf, changedtick, a.lines)
239
240
    def _on_buf_changedtick(self, buf, changedtick):
241
        a = self.nvim._session.attached_buffers[buf.handle]
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _session 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...
242
        a.changedtick = changedtick
243
        for cb in a.callbacks:
244
            cb(buf, changedtick, a.lines)
245
246
    def _on_buf_detach(self, buf, changedtick, first, last, data, more):
0 ignored issues
show
Unused Code introduced by
The argument more seems to be unused.
Loading history...
Unused Code introduced by
The argument changedtick seems to be unused.
Loading history...
Unused Code introduced by
The argument first seems to be unused.
Loading history...
Unused Code introduced by
The argument last seems to be unused.
Loading history...
Unused Code introduced by
The argument data seems to be unused.
Loading history...
247
        del self.nvim._session.attached_buffers[buf.handle]
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _session 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...
248
        for cb in a.callbacks:
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'a'
Loading history...
249
            cb(buf, -1, None)
250