]> crepu.dev Git - config.git/blame_incremental - djavu-asus/emacs/elpa/elpy-20230803.1455/elpy/jedibackend.py
Reorganización de directorios
[config.git] / djavu-asus / emacs / elpa / elpy-20230803.1455 / elpy / jedibackend.py
... / ...
CommitLineData
1"""Elpy backend using the Jedi library.
2
3This backend uses the Jedi library:
4
5https://github.com/davidhalter/jedi
6
7"""
8
9import sys
10import traceback
11import re
12
13import jedi
14
15from elpy import rpc
16from elpy.rpc import Fault
17
18# in case pkg_resources is not properly installed
19# (see https://github.com/jorgenschaefer/elpy/issues/1674).
20try:
21 from pkg_resources import parse_version
22except 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)
27JEDISUP17 = parse_version(jedi.__version__) >= parse_version("0.17.0")
28JEDISUP18 = parse_version(jedi.__version__) >= parse_version("0.18.0")
29
30
31class 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
642def 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
656def 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
678def 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)