]>
Commit | Line | Data |
---|---|---|
1 | """Elpy backend using the Jedi library. | |
2 | ||
3 | This backend uses the Jedi library: | |
4 | ||
5 | https://github.com/davidhalter/jedi | |
6 | ||
7 | """ | |
8 | ||
9 | import sys | |
10 | import traceback | |
11 | import re | |
12 | ||
13 | import jedi | |
14 | ||
15 | from elpy import rpc | |
16 | from elpy.rpc import Fault | |
17 | ||
18 | # in case pkg_resources is not properly installed | |
19 | # (see https://github.com/jorgenschaefer/elpy/issues/1674). | |
20 | try: | |
21 | from pkg_resources import parse_version | |
22 | except ImportError: # pragma: no cover | |
23 | def parse_version(*arg, **kwargs): | |
24 | raise Fault("`pkg_resources` could not be imported, " | |
25 | "please reinstall Elpy RPC virtualenv with" | |
26 | " `M-x elpy-rpc-reinstall-virtualenv`", code=400) | |
27 | JEDISUP17 = parse_version(jedi.__version__) >= parse_version("0.17.0") | |
28 | JEDISUP18 = parse_version(jedi.__version__) >= parse_version("0.18.0") | |
29 | ||
30 | ||
31 | class JediBackend(object): | |
32 | """The Jedi backend class. | |
33 | ||
34 | Implements the RPC calls we can pass on to Jedi. | |
35 | ||
36 | Documentation: http://jedi.jedidjah.ch/en/latest/docs/plugin-api.html | |
37 | ||
38 | """ | |
39 | name = "jedi" | |
40 | ||
41 | def __init__(self, project_root, environment_binaries_path): | |
42 | self.project_root = project_root | |
43 | self.environment = None | |
44 | if environment_binaries_path is not None: | |
45 | self.environment = jedi.create_environment(environment_binaries_path, | |
46 | safe=False) | |
47 | self.completions = {} | |
48 | sys.path.append(project_root) | |
49 | # Backward compatibility to jedi<17 | |
50 | if not JEDISUP17: # pragma: no cover | |
51 | self.rpc_get_completions = self.rpc_get_completions_jedi16 | |
52 | self.rpc_get_docstring = self.rpc_get_docstring_jedi16 | |
53 | self.rpc_get_definition = self.rpc_get_definition_jedi16 | |
54 | self.rpc_get_assignment = self.rpc_get_assignment_jedi16 | |
55 | self.rpc_get_calltip = self.rpc_get_calltip_jedi16 | |
56 | self.rpc_get_oneline_docstring = self.rpc_get_oneline_docstring_jedi16 | |
57 | self.rpc_get_usages = self.rpc_get_usages_jedi16 | |
58 | self.rpc_get_names = self.rpc_get_names_jedi16 | |
59 | ||
60 | def rpc_get_completions(self, filename, source, offset): | |
61 | line, column = pos_to_linecol(source, offset) | |
62 | proposals = run_with_debug(jedi, 'complete', code=source, | |
63 | path=filename, | |
64 | environment=self.environment, | |
65 | fun_kwargs={'line': line, 'column': column}) | |
66 | self.completions = dict((proposal.name, proposal) | |
67 | for proposal in proposals) | |
68 | return [{'name': proposal.name.rstrip("="), | |
69 | 'suffix': proposal.complete.rstrip("="), | |
70 | 'annotation': proposal.type, | |
71 | 'meta': proposal.description} | |
72 | for proposal in proposals] | |
73 | ||
74 | def rpc_get_completions_jedi16(self, filename, source, offset): | |
75 | # Backward compatibility to jedi<17 | |
76 | line, column = pos_to_linecol(source, offset) | |
77 | proposals = run_with_debug(jedi, 'completions', | |
78 | source=source, line=line, column=column, | |
79 | path=filename, encoding='utf-8', | |
80 | environment=self.environment) | |
81 | if proposals is None: | |
82 | return [] | |
83 | self.completions = dict((proposal.name, proposal) | |
84 | for proposal in proposals) | |
85 | return [{'name': proposal.name.rstrip("="), | |
86 | 'suffix': proposal.complete.rstrip("="), | |
87 | 'annotation': proposal.type, | |
88 | 'meta': proposal.description} | |
89 | for proposal in proposals] | |
90 | ||
91 | def rpc_get_completion_docstring(self, completion): | |
92 | proposal = self.completions.get(completion) | |
93 | if proposal is None: | |
94 | return None | |
95 | else: | |
96 | return proposal.docstring(fast=False) | |
97 | ||
98 | def rpc_get_completion_location(self, completion): | |
99 | proposal = self.completions.get(completion) | |
100 | if proposal is None: | |
101 | return None | |
102 | else: | |
103 | return (proposal.module_path, proposal.line) | |
104 | ||
105 | def rpc_get_docstring(self, filename, source, offset): | |
106 | line, column = pos_to_linecol(source, offset) | |
107 | locations = run_with_debug(jedi, 'goto', | |
108 | code=source, | |
109 | path=filename, | |
110 | environment=self.environment, | |
111 | fun_kwargs={'line': line, | |
112 | 'column': column, | |
113 | 'follow_imports': True, | |
114 | 'follow_builtin_imports': True}) | |
115 | if not locations: | |
116 | return None | |
117 | # Filter uninteresting things | |
118 | if locations[-1].name in ["str", "int", "float", "bool", "tuple", | |
119 | "list", "dict"]: | |
120 | return None | |
121 | if locations[-1].docstring(): | |
122 | return ('Documentation for {0}:\n\n'.format( | |
123 | locations[-1].full_name) + locations[-1].docstring()) | |
124 | else: | |
125 | return None | |
126 | ||
127 | def rpc_get_docstring_jedi16(self, filename, source, offset): | |
128 | # Backward compatibility to jedi<17 | |
129 | line, column = pos_to_linecol(source, offset) | |
130 | locations = run_with_debug(jedi, 'goto_definitions', | |
131 | source=source, line=line, column=column, | |
132 | path=filename, encoding='utf-8', | |
133 | environment=self.environment) | |
134 | if not locations: | |
135 | return None | |
136 | # Filter uninteresting things | |
137 | if locations[-1].name in ["str", "int", "float", "bool", "tuple", | |
138 | "list", "dict"]: | |
139 | return None | |
140 | if locations[-1].docstring(): | |
141 | return ('Documentation for {0}:\n\n'.format( | |
142 | locations[-1].full_name) + locations[-1].docstring()) | |
143 | else: | |
144 | return None | |
145 | ||
146 | def rpc_get_definition(self, filename, source, offset): | |
147 | line, column = pos_to_linecol(source, offset) | |
148 | locations = run_with_debug(jedi, 'goto', | |
149 | code=source, | |
150 | path=filename, | |
151 | environment=self.environment, | |
152 | fun_kwargs={'line': line, | |
153 | 'column': column, | |
154 | 'follow_imports': True, | |
155 | 'follow_builtin_imports': True}) | |
156 | if not locations: | |
157 | return None | |
158 | # goto_definitions() can return silly stuff like __builtin__ | |
159 | # for int variables, so we remove them. See issue #76. | |
160 | locations = [ | |
161 | loc for loc in locations | |
162 | if (loc.module_path is not None | |
163 | and loc.module_name != 'builtins' | |
164 | and loc.module_name != '__builtin__')] | |
165 | if len(locations) == 0: | |
166 | return None | |
167 | loc = locations[-1] | |
168 | try: | |
169 | if loc.module_path == filename: | |
170 | offset = linecol_to_pos(source, | |
171 | loc.line, | |
172 | loc.column) | |
173 | else: | |
174 | with open(loc.module_path) as f: | |
175 | offset = linecol_to_pos(f.read(), | |
176 | loc.line, | |
177 | loc.column) | |
178 | except IOError: # pragma: no cover | |
179 | return None | |
180 | return (loc.module_path, offset) | |
181 | ||
182 | def rpc_get_definition_jedi16(self, filename, source, offset): # pragma: no cover | |
183 | # Backward compatibility to jedi<17 | |
184 | line, column = pos_to_linecol(source, offset) | |
185 | locations = run_with_debug(jedi, 'goto_definitions', | |
186 | source=source, line=line, column=column, | |
187 | path=filename, encoding='utf-8', | |
188 | environment=self.environment) | |
189 | # goto_definitions() can return silly stuff like __builtin__ | |
190 | # for int variables, so we fall back on goto() in those | |
191 | # cases. See issue #76. | |
192 | if ( | |
193 | locations and | |
194 | (locations[0].module_path is None | |
195 | or locations[0].module_name == 'builtins' | |
196 | or locations[0].module_name == '__builtin__') | |
197 | ): | |
198 | locations = run_with_debug(jedi, 'goto_assignments', | |
199 | source=source, line=line, | |
200 | column=column, | |
201 | path=filename, | |
202 | encoding='utf-8', | |
203 | environment=self.environment) | |
204 | if not locations: | |
205 | return None | |
206 | else: | |
207 | loc = locations[-1] | |
208 | try: | |
209 | if loc.module_path: | |
210 | if loc.module_path == filename: | |
211 | offset = linecol_to_pos(source, | |
212 | loc.line, | |
213 | loc.column) | |
214 | else: | |
215 | with open(loc.module_path) as f: | |
216 | offset = linecol_to_pos(f.read(), | |
217 | loc.line, | |
218 | loc.column) | |
219 | else: | |
220 | return None | |
221 | except IOError: | |
222 | return None | |
223 | return (loc.module_path, offset) | |
224 | ||
225 | def rpc_get_assignment(self, filename, source, offset): | |
226 | raise Fault("Obsolete since jedi 17.0. Please use 'get_definition'.") | |
227 | ||
228 | def rpc_get_assignment_jedi16(self, filename, source, offset): # pragma: no cover | |
229 | # Backward compatibility to jedi<17 | |
230 | line, column = pos_to_linecol(source, offset) | |
231 | locations = run_with_debug(jedi, 'goto_assignments', | |
232 | source=source, line=line, column=column, | |
233 | path=filename, encoding='utf-8', | |
234 | environment=self.environment) | |
235 | ||
236 | if not locations: | |
237 | return None | |
238 | else: | |
239 | loc = locations[-1] | |
240 | try: | |
241 | if loc.module_path: | |
242 | if loc.module_path == filename: | |
243 | offset = linecol_to_pos(source, | |
244 | loc.line, | |
245 | loc.column) | |
246 | else: | |
247 | with open(loc.module_path) as f: | |
248 | offset = linecol_to_pos(f.read(), | |
249 | loc.line, | |
250 | loc.column) | |
251 | else: | |
252 | return None | |
253 | except IOError: | |
254 | return None | |
255 | return (loc.module_path, offset) | |
256 | ||
257 | def rpc_get_calltip(self, filename, source, offset): | |
258 | line, column = pos_to_linecol(source, offset) | |
259 | calls = run_with_debug(jedi, 'get_signatures', | |
260 | code=source, | |
261 | path=filename, | |
262 | environment=self.environment, | |
263 | fun_kwargs={'line': line, | |
264 | 'column': column}) | |
265 | if not calls: | |
266 | return None | |
267 | params = [re.sub("^param ", '', param.description) | |
268 | for param in calls[0].params] | |
269 | return {"name": calls[0].name, | |
270 | "index": calls[0].index, | |
271 | "params": params} | |
272 | ||
273 | def rpc_get_calltip_jedi16(self, filename, source, offset): # pragma: no cover | |
274 | # Backward compatibility to jedi<17 | |
275 | line, column = pos_to_linecol(source, offset) | |
276 | calls = run_with_debug(jedi, 'call_signatures', | |
277 | source=source, line=line, column=column, | |
278 | path=filename, encoding='utf-8', | |
279 | environment=self.environment) | |
280 | if calls: | |
281 | call = calls[0] | |
282 | else: | |
283 | call = None | |
284 | if not call: | |
285 | return None | |
286 | # Strip 'param' added by jedi at the beginning of | |
287 | # parameter names. Should be unecessary for jedi > 0.13.0 | |
288 | params = [re.sub("^param ", '', param.description) | |
289 | for param in call.params] | |
290 | return {"name": call.name, | |
291 | "index": call.index, | |
292 | "params": params} | |
293 | ||
294 | def rpc_get_calltip_or_oneline_docstring(self, filename, source, offset): | |
295 | """ | |
296 | Return the current function calltip or its oneline docstring. | |
297 | ||
298 | Meant to be used with eldoc. | |
299 | """ | |
300 | # Try to get a oneline docstring then | |
301 | docs = self.rpc_get_oneline_docstring(filename=filename, | |
302 | source=source, | |
303 | offset=offset) | |
304 | if docs is not None: | |
305 | if docs['doc'] != "No documentation": | |
306 | docs['kind'] = 'oneline_doc' | |
307 | return docs | |
308 | # Try to get a calltip | |
309 | calltip = self.rpc_get_calltip(filename=filename, source=source, | |
310 | offset=offset) | |
311 | if calltip is not None: | |
312 | calltip['kind'] = 'calltip' | |
313 | return calltip | |
314 | # Ok, no calltip, just display the function name | |
315 | if docs is not None: | |
316 | docs['kind'] = 'oneline_doc' | |
317 | return docs | |
318 | # Giving up... | |
319 | return None | |
320 | ||
321 | def rpc_get_oneline_docstring(self, filename, source, offset): | |
322 | """Return a oneline docstring for the symbol at offset""" | |
323 | line, column = pos_to_linecol(source, offset) | |
324 | definitions = run_with_debug(jedi, 'goto', | |
325 | code=source, | |
326 | path=filename, | |
327 | environment=self.environment, | |
328 | fun_kwargs={'line': line, | |
329 | 'column': column}) | |
330 | if not definitions: | |
331 | return None | |
332 | # avoid unintersting stuff | |
333 | definitions = [defi for defi in definitions | |
334 | if defi.name not in | |
335 | ["str", "int", "float", "bool", "tuple", | |
336 | "list", "dict"]] | |
337 | if len(definitions) == 0: | |
338 | return None | |
339 | definition = definitions[0] | |
340 | # Get name | |
341 | if definition.type in ['function', 'class']: | |
342 | raw_name = definition.name | |
343 | name = '{}()'.format(raw_name) | |
344 | doc = definition.docstring().split('\n') | |
345 | elif definition.type in ['module']: | |
346 | raw_name = definition.name | |
347 | name = '{} {}'.format(raw_name, definition.type) | |
348 | doc = definition.docstring().split('\n') | |
349 | elif (definition.type in ['instance'] | |
350 | and hasattr(definition, "name")): | |
351 | raw_name = definition.name | |
352 | name = raw_name | |
353 | doc = definition.docstring().split('\n') | |
354 | else: | |
355 | return None | |
356 | # Keep only the first paragraph that is not a function declaration | |
357 | lines = [] | |
358 | call = "{}(".format(raw_name) | |
359 | # last line | |
360 | doc.append('') | |
361 | for i in range(len(doc)): | |
362 | if doc[i] == '' and len(lines) != 0: | |
363 | paragraph = " ".join(lines) | |
364 | lines = [] | |
365 | if call != paragraph[0:len(call)]: | |
366 | break | |
367 | paragraph = "" | |
368 | continue | |
369 | lines.append(doc[i]) | |
370 | # Keep only the first sentence | |
371 | onelinedoc = paragraph.split('. ', 1) | |
372 | if len(onelinedoc) == 2: | |
373 | onelinedoc = onelinedoc[0] + '.' | |
374 | else: | |
375 | onelinedoc = onelinedoc[0] | |
376 | if onelinedoc == '': | |
377 | onelinedoc = "No documentation" | |
378 | return {"name": name, | |
379 | "doc": onelinedoc} | |
380 | ||
381 | def rpc_get_oneline_docstring_jedi16(self, filename, source, offset): # pragma: no cover | |
382 | """Return a oneline docstring for the symbol at offset""" | |
383 | # Backward compatibility to jedi<17 | |
384 | line, column = pos_to_linecol(source, offset) | |
385 | definitions = run_with_debug(jedi, 'goto_definitions', | |
386 | source=source, line=line, column=column, | |
387 | path=filename, encoding='utf-8', | |
388 | environment=self.environment) | |
389 | # avoid unintersting stuff | |
390 | try: | |
391 | if definitions[0].name in ["str", "int", "float", "bool", "tuple", | |
392 | "list", "dict"]: | |
393 | return None | |
394 | except: | |
395 | pass | |
396 | assignments = run_with_debug(jedi, 'goto_assignments', | |
397 | source=source, line=line, column=column, | |
398 | path=filename, encoding='utf-8', | |
399 | environment=self.environment) | |
400 | ||
401 | if definitions: | |
402 | definition = definitions[0] | |
403 | else: | |
404 | definition = None | |
405 | if assignments: | |
406 | assignment = assignments[0] | |
407 | else: | |
408 | assignment = None | |
409 | if definition: | |
410 | # Get name | |
411 | if definition.type in ['function', 'class']: | |
412 | raw_name = definition.name | |
413 | name = '{}()'.format(raw_name) | |
414 | doc = definition.docstring().split('\n') | |
415 | elif definition.type in ['module']: | |
416 | raw_name = definition.name | |
417 | name = '{} {}'.format(raw_name, definition.type) | |
418 | doc = definition.docstring().split('\n') | |
419 | elif (definition.type in ['instance'] | |
420 | and hasattr(assignment, "name")): | |
421 | raw_name = assignment.name | |
422 | name = raw_name | |
423 | doc = assignment.docstring().split('\n') | |
424 | else: | |
425 | return None | |
426 | # Keep only the first paragraph that is not a function declaration | |
427 | lines = [] | |
428 | call = "{}(".format(raw_name) | |
429 | # last line | |
430 | doc.append('') | |
431 | for i in range(len(doc)): | |
432 | if doc[i] == '' and len(lines) != 0: | |
433 | paragraph = " ".join(lines) | |
434 | lines = [] | |
435 | if call != paragraph[0:len(call)]: | |
436 | break | |
437 | paragraph = "" | |
438 | continue | |
439 | lines.append(doc[i]) | |
440 | # Keep only the first sentence | |
441 | onelinedoc = paragraph.split('. ', 1) | |
442 | if len(onelinedoc) == 2: | |
443 | onelinedoc = onelinedoc[0] + '.' | |
444 | else: | |
445 | onelinedoc = onelinedoc[0] | |
446 | if onelinedoc == '': | |
447 | onelinedoc = "No documentation" | |
448 | return {"name": name, | |
449 | "doc": onelinedoc} | |
450 | return None | |
451 | ||
452 | def rpc_get_usages(self, filename, source, offset): | |
453 | """Return the uses of the symbol at offset. | |
454 | ||
455 | Returns a list of occurrences of the symbol, as dicts with the | |
456 | fields name, filename, and offset. | |
457 | ||
458 | """ | |
459 | line, column = pos_to_linecol(source, offset) | |
460 | uses = run_with_debug(jedi, 'get_references', | |
461 | code=source, | |
462 | path=filename, | |
463 | environment=self.environment, | |
464 | fun_kwargs={'line': line, | |
465 | 'column': column}) | |
466 | if uses is None: | |
467 | return None | |
468 | result = [] | |
469 | for use in uses: | |
470 | if use.module_path == filename: | |
471 | offset = linecol_to_pos(source, use.line, use.column) | |
472 | elif use.module_path is not None: | |
473 | with open(use.module_path) as f: | |
474 | text = f.read() | |
475 | offset = linecol_to_pos(text, use.line, use.column) | |
476 | result.append({"name": use.name, | |
477 | "filename": use.module_path, | |
478 | "offset": offset}) | |
479 | return result | |
480 | ||
481 | def rpc_get_usages_jedi16(self, filename, source, offset): # pragma: no cover | |
482 | """Return the uses of the symbol at offset. | |
483 | ||
484 | Returns a list of occurrences of the symbol, as dicts with the | |
485 | fields name, filename, and offset. | |
486 | ||
487 | """ | |
488 | # Backward compatibility to jedi<17 | |
489 | line, column = pos_to_linecol(source, offset) | |
490 | uses = run_with_debug(jedi, 'usages', | |
491 | source=source, line=line, column=column, | |
492 | path=filename, encoding='utf-8', | |
493 | environment=self.environment) | |
494 | if uses is None: | |
495 | return None | |
496 | result = [] | |
497 | for use in uses: | |
498 | if use.module_path == filename: | |
499 | offset = linecol_to_pos(source, use.line, use.column) | |
500 | elif use.module_path is not None: | |
501 | with open(use.module_path) as f: | |
502 | text = f.read() | |
503 | offset = linecol_to_pos(text, use.line, use.column) | |
504 | ||
505 | result.append({"name": use.name, | |
506 | "filename": use.module_path, | |
507 | "offset": offset}) | |
508 | ||
509 | return result | |
510 | ||
511 | def rpc_get_names(self, filename, source, offset): | |
512 | """Return the list of possible names""" | |
513 | names = run_with_debug(jedi, 'get_names', | |
514 | code=source, | |
515 | path=filename, | |
516 | environment=self.environment, | |
517 | fun_kwargs={'all_scopes': True, | |
518 | 'definitions': True, | |
519 | 'references': True}) | |
520 | result = [] | |
521 | for name in names: | |
522 | if name.module_path == filename: | |
523 | offset = linecol_to_pos(source, name.line, name.column) | |
524 | elif name.module_path is not None: | |
525 | with open(name.module_path) as f: | |
526 | text = f.read() | |
527 | offset = linecol_to_pos(text, name.line, name.column) | |
528 | result.append({"name": name.name, | |
529 | "filename": name.module_path, | |
530 | "offset": offset}) | |
531 | return result | |
532 | ||
533 | def rpc_get_names_jedi16(self, filename, source, offset): # pragma: no cover | |
534 | """Return the list of possible names""" | |
535 | # Backward compatibility to jedi<17 | |
536 | names = jedi.api.names(source=source, | |
537 | path=filename, encoding='utf-8', | |
538 | all_scopes=True, | |
539 | definitions=True, | |
540 | references=True) | |
541 | ||
542 | result = [] | |
543 | for name in names: | |
544 | if name.module_path == filename: | |
545 | offset = linecol_to_pos(source, name.line, name.column) | |
546 | elif name.module_path is not None: | |
547 | with open(name.module_path) as f: | |
548 | text = f.read() | |
549 | offset = linecol_to_pos(text, name.line, name.column) | |
550 | result.append({"name": name.name, | |
551 | "filename": name.module_path, | |
552 | "offset": offset}) | |
553 | return result | |
554 | ||
555 | def rpc_get_rename_diff(self, filename, source, offset, new_name): | |
556 | """Get the diff resulting from renaming the thing at point""" | |
557 | if not hasattr(jedi.Script, "rename"): # pragma: no cover | |
558 | return {'success': "Not available"} | |
559 | line, column = pos_to_linecol(source, offset) | |
560 | ren = run_with_debug(jedi, 'rename', code=source, | |
561 | path=filename, | |
562 | environment=self.environment, | |
563 | fun_kwargs={'line': line, | |
564 | 'column': column, | |
565 | 'new_name': new_name}) | |
566 | if ren is None: | |
567 | return {'success': False} | |
568 | else: | |
569 | return {'success': True, | |
570 | 'project_path': ren._inference_state.project._path, | |
571 | 'diff': ren.get_diff(), | |
572 | 'changed_files': list(ren.get_changed_files().keys())} | |
573 | ||
574 | def rpc_get_extract_variable_diff(self, filename, source, offset, new_name, | |
575 | line_beg, line_end, col_beg, col_end): | |
576 | """Get the diff resulting from extracting the selected code""" | |
577 | if not hasattr(jedi.Script, "extract_variable"): # pragma: no cover | |
578 | return {'success': "Not available"} | |
579 | ren = run_with_debug(jedi, 'extract_variable', code=source, | |
580 | path=filename, | |
581 | environment=self.environment, | |
582 | fun_kwargs={'line': line_beg, | |
583 | 'until_line': line_end, | |
584 | 'column': col_beg, | |
585 | 'until_column': col_end, | |
586 | 'new_name': new_name}) | |
587 | if ren is None: | |
588 | return {'success': False} | |
589 | else: | |
590 | return {'success': True, | |
591 | 'project_path': ren._inference_state.project._path, | |
592 | 'diff': ren.get_diff(), | |
593 | 'changed_files': list(ren.get_changed_files().keys())} | |
594 | ||
595 | def rpc_get_extract_function_diff(self, filename, source, offset, new_name, | |
596 | line_beg, line_end, col_beg, col_end): | |
597 | """Get the diff resulting from extracting the selected code""" | |
598 | if not hasattr(jedi.Script, "extract_function"): # pragma: no cover | |
599 | return {'success': "Not available"} | |
600 | ren = run_with_debug(jedi, 'extract_function', code=source, | |
601 | path=filename, | |
602 | environment=self.environment, | |
603 | fun_kwargs={'line': line_beg, | |
604 | 'until_line': line_end, | |
605 | 'column': col_beg, | |
606 | 'until_column': col_end, | |
607 | 'new_name': new_name}) | |
608 | if ren is None: | |
609 | return {'success': False} | |
610 | else: | |
611 | return {'success': True, | |
612 | 'project_path': ren._inference_state.project._path, | |
613 | 'diff': ren.get_diff(), | |
614 | 'changed_files': list(ren.get_changed_files().keys())} | |
615 | ||
616 | def rpc_get_inline_diff(self, filename, source, offset): | |
617 | """Get the diff resulting from inlining the selected variable""" | |
618 | if not hasattr(jedi.Script, "inline"): # pragma: no cover | |
619 | return {'success': "Not available"} | |
620 | line, column = pos_to_linecol(source, offset) | |
621 | ren = run_with_debug(jedi, 'inline', code=source, | |
622 | path=filename, | |
623 | environment=self.environment, | |
624 | fun_kwargs={'line': line, | |
625 | 'column': column}) | |
626 | if ren is None: | |
627 | return {'success': False} | |
628 | else: | |
629 | return {'success': True, | |
630 | 'project_path': ren._inference_state.project._path, | |
631 | 'diff': ren.get_diff(), | |
632 | 'changed_files': list(ren.get_changed_files().keys())} | |
633 | ||
634 | ||
635 | # From the Jedi documentation: | |
636 | # | |
637 | # line is the current line you want to perform actions on (starting | |
638 | # with line #1 as the first line). column represents the current | |
639 | # column/indent of the cursor (starting with zero). source_path | |
640 | # should be the path of your file in the file system. | |
641 | ||
642 | def pos_to_linecol(text, pos): | |
643 | """Return a tuple of line and column for offset pos in text. | |
644 | ||
645 | Lines are one-based, columns zero-based. | |
646 | ||
647 | This is how Jedi wants it. Don't ask me why. | |
648 | ||
649 | """ | |
650 | line_start = text.rfind("\n", 0, pos) + 1 | |
651 | line = text.count("\n", 0, line_start) + 1 | |
652 | col = pos - line_start | |
653 | return line, col | |
654 | ||
655 | ||
656 | def linecol_to_pos(text, line, col): | |
657 | """Return the offset of this line and column in text. | |
658 | ||
659 | Lines are one-based, columns zero-based. | |
660 | ||
661 | This is how Jedi wants it. Don't ask me why. | |
662 | ||
663 | """ | |
664 | nth_newline_offset = 0 | |
665 | for i in range(line - 1): | |
666 | new_offset = text.find("\n", nth_newline_offset) | |
667 | if new_offset < 0: | |
668 | raise ValueError("Text does not have {0} lines." | |
669 | .format(line)) | |
670 | nth_newline_offset = new_offset + 1 | |
671 | offset = nth_newline_offset + col | |
672 | if offset > len(text): | |
673 | raise ValueError("Line {0} column {1} is not within the text" | |
674 | .format(line, col)) | |
675 | return offset | |
676 | ||
677 | ||
678 | def run_with_debug(jedi, name, fun_kwargs={}, *args, **kwargs): | |
679 | re_raise = kwargs.pop('re_raise', ()) | |
680 | try: | |
681 | script = jedi.Script(*args, **kwargs) | |
682 | return getattr(script, name)(**fun_kwargs) | |
683 | except Exception as e: | |
684 | if isinstance(e, re_raise): | |
685 | raise | |
686 | if JEDISUP17: | |
687 | if isinstance(e, jedi.RefactoringError): | |
688 | return None | |
689 | # Bug jedi#485 | |
690 | if ( | |
691 | isinstance(e, ValueError) and | |
692 | "invalid \\x escape" in str(e) | |
693 | ): | |
694 | return None | |
695 | # Bug jedi#485 in Python 3 | |
696 | if ( | |
697 | isinstance(e, SyntaxError) and | |
698 | "truncated \\xXX escape" in str(e) | |
699 | ): | |
700 | return None | |
701 | ||
702 | from jedi import debug | |
703 | ||
704 | debug_info = [] | |
705 | ||
706 | def _debug(level, str_out): | |
707 | if level == debug.NOTICE: | |
708 | prefix = "[N]" | |
709 | elif level == debug.WARNING: | |
710 | prefix = "[W]" | |
711 | else: | |
712 | prefix = "[?]" | |
713 | debug_info.append(u"{0} {1}".format(prefix, str_out)) | |
714 | ||
715 | jedi.set_debug_function(_debug, speed=False) | |
716 | try: | |
717 | script = jedi.Script(*args, **kwargs) | |
718 | return getattr(script, name)() | |
719 | except Exception as e: | |
720 | source = kwargs.get('source') | |
721 | sc_args = [] | |
722 | sc_args.extend(repr(arg) for arg in args) | |
723 | sc_args.extend("{0}={1}".format(k, "source" if k == "source" | |
724 | else repr(v)) | |
725 | for (k, v) in kwargs.items()) | |
726 | ||
727 | data = { | |
728 | "traceback": traceback.format_exc(), | |
729 | "jedi_debug_info": {'script_args': ", ".join(sc_args), | |
730 | 'source': source, | |
731 | 'method': name, | |
732 | 'debug_info': debug_info} | |
733 | } | |
734 | raise rpc.Fault(message=str(e), | |
735 | code=500, | |
736 | data=data) | |
737 | finally: | |
738 | jedi.set_debug_function(None) |