Completed
Push — master ( 4280e4...9ac47f )
by Björn
9s
created

ScriptHost.python_do_range()   C

Complexity

Conditions 8

Size

Total Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 66.9547

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
c 1
b 0
f 0
dl 0
loc 57
ccs 1
cts 37
cp 0.027
crap 66.9547
rs 6.0451

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
"""Legacy python/python3-vim emulation."""
2 6
import imp
3 6
import io
4 6
import logging
5 6
import os
6 6
import sys
7
8 6
from .decorators import plugin, rpc_export
9 6
from ..api import Nvim, walk
10 6
from ..msgpack_rpc import ErrorResponse
11 6
from ..util import format_exc_skip
12
13 6
__all__ = ('ScriptHost',)
14
15
16 6
logger = logging.getLogger(__name__)
17 6
debug, info, warn = (logger.debug, logger.info, logger.warn,)
18
19 6
IS_PYTHON3 = sys.version_info >= (3, 0)
20
21 6
if IS_PYTHON3:
22 3
    basestring = str
23
24 3
    if sys.version_info >= (3, 4):
25 2
        from importlib.machinery import PathFinder
26
27
28 6
@plugin
29 6
class ScriptHost(object):
30
31
    """Provides an environment for running python plugins created for Vim."""
32
33 6
    def __init__(self, nvim):
34
        """Initialize the legacy python-vim environment."""
35
        self.setup(nvim)
36
        # context where all code will run
37
        self.module = imp.new_module('__main__')
38
        nvim.script_context = self.module
39
        # it seems some plugins assume 'sys' is already imported, so do it now
40
        exec('import sys', self.module.__dict__)
0 ignored issues
show
Coding Style Security introduced by
The use of exec is discouraged.

Execution of dynamic code might introduce security vulnerabilities. It is generally recommended to use this feature with care and only when necessary.

Loading history...
41
        self.legacy_vim = LegacyVim.from_nvim(nvim)
42
        sys.modules['vim'] = self.legacy_vim
43
44 6
    def setup(self, nvim):
45
        """Setup import hooks and global streams.
46
47
        This will add import hooks for importing modules from runtime
48
        directories and patch the sys module so 'print' calls will be
49
        forwarded to Nvim.
50
        """
51
        self.nvim = nvim
52
        info('install import hook/path')
53
        self.hook = path_hook(nvim)
54
        sys.path_hooks.append(self.hook)
55
        nvim.VIM_SPECIAL_PATH = '_vim_path_'
56
        sys.path.append(nvim.VIM_SPECIAL_PATH)
57
        info('redirect sys.stdout and sys.stderr')
58
        self.saved_stdout = sys.stdout
59
        self.saved_stderr = sys.stderr
60
        sys.stdout = RedirectStream(lambda data: nvim.out_write(data))
0 ignored issues
show
Unused Code introduced by
This lambda might be unnecessary.
Loading history...
61
        sys.stderr = RedirectStream(lambda data: nvim.err_write(data))
0 ignored issues
show
Unused Code introduced by
This lambda might be unnecessary.
Loading history...
62
63 6
    def teardown(self):
64
        """Restore state modified from the `setup` call."""
65
        nvim = self.nvim
66
        info('uninstall import hook/path')
67
        sys.path.remove(nvim.VIM_SPECIAL_PATH)
68
        sys.path_hooks.remove(self.hook)
69
        info('restore sys.stdout and sys.stderr')
70
        sys.stdout = self.saved_stdout
71
        sys.stderr = self.saved_stderr
72
73 6
    @rpc_export('python_execute', sync=True)
74
    def python_execute(self, script, range_start, range_stop):
75
        """Handle the `python` ex command."""
76
        self._set_current_range(range_start, range_stop)
77
        try:
78
            exec(script, self.module.__dict__)
0 ignored issues
show
Coding Style Security introduced by
The use of exec is discouraged.

Execution of dynamic code might introduce security vulnerabilities. It is generally recommended to use this feature with care and only when necessary.

Loading history...
79
        except Exception:
80
            raise ErrorResponse(format_exc_skip(1))
81
82 6
    @rpc_export('python_execute_file', sync=True)
83
    def python_execute_file(self, file_path, range_start, range_stop):
84
        """Handle the `pyfile` ex command."""
85
        self._set_current_range(range_start, range_stop)
86
        with open(file_path) as f:
87
            script = compile(f.read(), file_path, 'exec')
88
            try:
89
                exec(script, self.module.__dict__)
0 ignored issues
show
Coding Style Security introduced by
The use of exec is discouraged.

Execution of dynamic code might introduce security vulnerabilities. It is generally recommended to use this feature with care and only when necessary.

Loading history...
90
            except Exception:
91
                raise ErrorResponse(format_exc_skip(1))
92
93 6
    @rpc_export('python_do_range', sync=True)
94
    def python_do_range(self, start, stop, code):
95
        """Handle the `pydo` ex command."""
96
        self._set_current_range(start, stop)
97
        nvim = self.nvim
98
        start -= 1
99
        stop -= 1
100
        fname = '_vim_pydo'
101
102
        # define the function
103
        function_def = 'def %s(line, linenr):\n %s' % (fname, code,)
104
        exec(function_def, self.module.__dict__)
0 ignored issues
show
Coding Style Security introduced by
The use of exec is discouraged.

Execution of dynamic code might introduce security vulnerabilities. It is generally recommended to use this feature with care and only when necessary.

Loading history...
105
        # get the function
106
        function = self.module.__dict__[fname]
107
        while start <= stop:
108
            # Process batches of 5000 to avoid the overhead of making multiple
109
            # API calls for every line. Assuming an average line length of 100
110
            # bytes, approximately 488 kilobytes will be transferred per batch,
111
            # which can be done very quickly in a single API call.
112
            sstart = start
113
            sstop = min(start + 5000, stop)
114
            lines = nvim.current.buffer.get_line_slice(sstart, sstop, True,
115
                                                       True)
116
117
            exception = None
118
            newlines = []
119
            linenr = sstart + 1
120
            for i, line in enumerate(lines):
0 ignored issues
show
Unused Code introduced by
The variable i seems to be unused.
Loading history...
121
                result = function(line, linenr)
122
                if result is None:
123
                    # Update earlier lines, and skip to the next
124
                    if newlines:
125
                        end = sstart + len(newlines) - 1
126
                        nvim.current.buffer.set_line_slice(sstart, end,
127
                                                           True, True,
128
                                                           newlines)
129
                    sstart += len(newlines) + 1
130
                    newlines = []
131
                    pass
0 ignored issues
show
Unused Code introduced by
Unnecessary pass statement
Loading history...
132
                elif isinstance(result, basestring):
133
                    newlines.append(result)
134
                else:
135
                    exception = TypeError('pydo should return a string ' +
136
                                          'or None, found %s instead'
137
                                          % result.__class__.__name__)
138
                    break
139
                linenr += 1
140
141
            start = sstop + 1
142
            if newlines:
143
                end = sstart + len(newlines) - 1
144
                nvim.current.buffer.set_line_slice(sstart, end, True, True,
145
                                                   newlines)
146
            if exception:
147
                raise exception
0 ignored issues
show
Bug introduced by
Raising NoneType while only classes or instances are allowed
Loading history...
148
        # delete the function
149
        del self.module.__dict__[fname]
150
151 6
    @rpc_export('python_eval', sync=True)
152
    def python_eval(self, expr):
153
        """Handle the `pyeval` vim function."""
154
        return eval(expr, self.module.__dict__)
0 ignored issues
show
Security Best Practice introduced by
eval should generally only be used if absolutely necessary.

The usage of eval might allow for executing arbitrary code if a user manages to inject dynamic input. Please use this language feature with care and only when you are sure of the input.

Loading history...
155
156 6
    def _set_current_range(self, start, stop):
157
        current = self.legacy_vim.current
158
        current.range = current.buffer.range(start, stop)
159
160
161 6
class RedirectStream(io.IOBase):
162 6
    def __init__(self, redirect_handler):
163
        self.redirect_handler = redirect_handler
164
165 6
    def write(self, data):
166
        self.redirect_handler(data)
167
168 6
    def writelines(self, seq):
169
        self.redirect_handler('\n'.join(seq))
170
171
172 6
if IS_PYTHON3:
173 3
    num_types = (int, float)
174
else:
175 3
    num_types = (int, long, float)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'long'
Loading history...
176
177
178 6
def num_to_str(obj):
179
    if isinstance(obj, num_types):
180
        return str(obj)
181
    else:
182
        return obj
183
184
185 6
class LegacyVim(Nvim):
186 6
    def eval(self, expr):
187
        obj = self.request("vim_eval", expr)
188
        return walk(num_to_str, obj)
189
190
191
# This was copied/adapted from nvim-python help
192 6
def path_hook(nvim):
193
    def _get_paths():
194
        return discover_runtime_directories(nvim)
195
196
    def _find_module(fullname, oldtail, path):
197
        idx = oldtail.find('.')
198
        if idx > 0:
199
            name = oldtail[:idx]
200
            tail = oldtail[idx + 1:]
201
            fmr = imp.find_module(name, path)
202
            module = imp.find_module(fullname[:-len(oldtail)] + name, *fmr)
203
            return _find_module(fullname, tail, module.__path__)
0 ignored issues
show
Bug introduced by
The Instance of tuple does not seem to have a member named __path__.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
204
        else:
205
            return imp.find_module(fullname, path)
206
207
    class VimModuleLoader(object):
208
        def __init__(self, module):
209
            self.module = module
210
211
        def load_module(self, fullname, path=None):
0 ignored issues
show
Unused Code introduced by
The argument path seems to be unused.
Loading history...
212
            # Check sys.modules, required for reload (see PEP302).
213
            if fullname in sys.modules:
214
                return sys.modules[fullname]
215
            return imp.load_module(fullname, *self.module)
216
217
    class VimPathFinder(object):
218
        @staticmethod
219
        def find_module(fullname, path=None):
220
            """Method for Python 2.7 and 3.3."""
221
            try:
222
                return VimModuleLoader(
223
                    _find_module(fullname, fullname, path or _get_paths()))
224
            except ImportError:
225
                return None
226
227
        @staticmethod
228
        def find_spec(fullname, path=None, target=None):
229
            """Method for Python 3.4+."""
230
            return PathFinder.find_spec(fullname, path or _get_paths(), target)
231
232
    def hook(path):
233
        if path == nvim.VIM_SPECIAL_PATH:
234
            return VimPathFinder
235
        else:
236
            raise ImportError
237
238
    return hook
239
240
241 6
def discover_runtime_directories(nvim):
242
    rv = []
243
    for path in nvim.list_runtime_paths():
244
        if not os.path.exists(path):
245
            continue
246
        path1 = os.path.join(path, 'pythonx')
247
        if IS_PYTHON3:
248
            path2 = os.path.join(path, 'python3')
249
        else:
250
            path2 = os.path.join(path, 'python2')
251
        if os.path.exists(path1):
252
            rv.append(path1)
253
        if os.path.exists(path2):
254
            rv.append(path2)
255
    return rv
256