Mercurial > hg > dotemacs
view elpa/elpy-1.14.1/elpy/jedibackend.py @ 156:c745e2cc79ee
elpy: update along with direct deps
author | Jordi Gutiérrez Hermoso <jordigh@octave.org> |
---|---|
date | Mon, 27 Feb 2017 12:17:38 -0500 |
parents | elpa/elpy-1.12.0/elpy/jedibackend.py@55ceabc58fcc |
children |
line wrap: on
line source
"""Elpy backend using the Jedi library. This backend uses the Jedi library: https://github.com/davidhalter/jedi """ import sys import traceback import jedi from elpy import rpc class JediBackend(object): """The Jedi backend class. Implements the RPC calls we can pass on to Jedi. Documentation: http://jedi.jedidjah.ch/en/latest/docs/plugin-api.html """ name = "jedi" def __init__(self, project_root): self.project_root = project_root self.completions = {} sys.path.append(project_root) def rpc_get_completions(self, filename, source, offset): line, column = pos_to_linecol(source, offset) proposals = run_with_debug(jedi, 'completions', source=source, line=line, column=column, path=filename, encoding='utf-8') if proposals is None: return [] self.completions = dict((proposal.name, proposal) for proposal in proposals) return [{'name': proposal.name.rstrip("="), 'suffix': proposal.complete.rstrip("="), 'annotation': proposal.type, 'meta': proposal.description} for proposal in proposals] def rpc_get_completion_docstring(self, completion): proposal = self.completions.get(completion) if proposal is None: return None else: return proposal.docstring(fast=False) def rpc_get_completion_location(self, completion): proposal = self.completions.get(completion) if proposal is None: return None else: return (proposal.module_path, proposal.line) def rpc_get_docstring(self, filename, source, offset): line, column = pos_to_linecol(source, offset) try: locations = run_with_debug(jedi, 'goto_definitions', source=source, line=line, column=column, path=filename, encoding='utf-8', re_raise=jedi.NotFoundError) except jedi.NotFoundError: return None if locations: return ('Documentation for {0}:\n\n'.format( locations[-1].full_name) + locations[-1].docstring()) else: return None def rpc_get_definition(self, filename, source, offset): line, column = pos_to_linecol(source, offset) try: locations = run_with_debug(jedi, 'goto_definitions', source=source, line=line, column=column, path=filename, encoding='utf-8', re_raise=jedi.NotFoundError) except jedi.NotFoundError: return None # goto_definitions() can return silly stuff like __builtin__ # for int variables, so we fall back on goto() in those # cases. See issue #76. if ( locations and locations[0].module_path is None ): locations = run_with_debug(jedi, 'goto_assignments', source=source, line=line, column=column, path=filename, encoding='utf-8') if not locations: return None else: loc = locations[-1] try: if loc.module_path: if loc.module_path == filename: offset = linecol_to_pos(source, loc.line, loc.column) else: with open(loc.module_path) as f: offset = linecol_to_pos(f.read(), loc.line, loc.column) else: return None except IOError: return None return (loc.module_path, offset) def rpc_get_calltip(self, filename, source, offset): line, column = pos_to_linecol(source, offset) calls = run_with_debug(jedi, 'call_signatures', source=source, line=line, column=column, path=filename, encoding='utf-8') if calls: call = calls[0] else: call = None if not call: return None try: call.index except AttributeError as e: if "get_definition" in str(e): # Bug #627 / jedi#573 return None elif "get_subscope_by_name" in str(e): # Bug #677 / jedi#628 return None else: raise return {"name": call.name, "index": call.index, "params": [param.description for param in call.params]} def rpc_get_usages(self, filename, source, offset): """Return the uses of the symbol at offset. Returns a list of occurrences of the symbol, as dicts with the fields name, filename, and offset. """ line, column = pos_to_linecol(source, offset) try: uses = run_with_debug(jedi, 'usages', source=source, line=line, column=column, path=filename, encoding='utf-8', re_raise=(jedi.NotFoundError,)) except jedi.NotFoundError: return [] if uses is None: return None result = [] for use in uses: if use.module_path == filename: offset = linecol_to_pos(source, use.line, use.column) elif use.module_path is not None: with open(use.module_path) as f: text = f.read() offset = linecol_to_pos(text, use.line, use.column) result.append({"name": use.name, "filename": use.module_path, "offset": offset}) return result # From the Jedi documentation: # # line is the current line you want to perform actions on (starting # with line #1 as the first line). column represents the current # column/indent of the cursor (starting with zero). source_path # should be the path of your file in the file system. def pos_to_linecol(text, pos): """Return a tuple of line and column for offset pos in text. Lines are one-based, columns zero-based. This is how Jedi wants it. Don't ask me why. """ line_start = text.rfind("\n", 0, pos) + 1 line = text.count("\n", 0, line_start) + 1 col = pos - line_start return line, col def linecol_to_pos(text, line, col): """Return the offset of this line and column in text. Lines are one-based, columns zero-based. This is how Jedi wants it. Don't ask me why. """ nth_newline_offset = 0 for i in range(line - 1): new_offset = text.find("\n", nth_newline_offset) if new_offset < 0: raise ValueError("Text does not have {0} lines." .format(line)) nth_newline_offset = new_offset + 1 offset = nth_newline_offset + col if offset > len(text): raise ValueError("Line {0} column {1} is not within the text" .format(line, col)) return offset def run_with_debug(jedi, name, *args, **kwargs): re_raise = kwargs.pop('re_raise', ()) # Remove form feed characters, they confuse Jedi (jedi#424) if 'source' in kwargs: kwargs['source'] = kwargs['source'].replace("\f", " ") try: script = jedi.Script(*args, **kwargs) return getattr(script, name)() except Exception as e: if isinstance(e, re_raise): raise # Bug jedi#417 if isinstance(e, TypeError) and str(e) == 'no dicts allowed': return None # Bug jedi#427 if isinstance(e, UnicodeDecodeError): return None # Bug jedi#429 if isinstance(e, IndexError): return None # Bug jedi#431 if isinstance(e, AttributeError) and str(e).endswith("'end_pos'"): return None # Bug in Python 2.6, see #275 if isinstance(e, OSError) and e.errno == 13: return None # Bug jedi#466 if ( isinstance(e, SyntaxError) and "EOL while scanning string literal" in str(e) ): return None # Bug jedi#482 if isinstance(e, UnicodeEncodeError): return None # Bug jedi#485 if ( isinstance(e, ValueError) and "invalid \\x escape" in str(e) ): return None # Bug jedi#485 in Python 3 if ( isinstance(e, SyntaxError) and "truncated \\xXX escape" in str(e) ): return None # Bug jedi#465 if ( isinstance(e, SyntaxError) and "encoding declaration in Unicode string" in str(e) ): return None # Bug #337 / jedi#471 if ( isinstance(e, ImportError) and "No module named" in str(e) ): return None # Bug #365 / jedi#486 - fixed in Jedi 0.8.2 if ( isinstance(e, UnboundLocalError) and "local variable 'path' referenced before assignment" in str(e) ): return None # Bug #366 / jedi#491 if ( isinstance(e, ValueError) and "__loader__ is None" in str(e) ): return None # Bug #353 if ( isinstance(e, OSError) and "No such file or directory" in str(e) ): return None # Bug #561, #564, #570, #588, #593, #599 / jedi#572, jedi#579, jedi#590 if isinstance(e, KeyError): return None # Bug #519 / jedi#610 if ( isinstance(e, RuntimeError) and "maximum recursion depth exceeded" in str(e) ): return None # Bug #563 / jedi#589 if ( isinstance(e, AttributeError) and "MergedNamesDict" in str(e) ): return None # Bug #615 / jedi#592 if ( isinstance(e, AttributeError) and "ListComprehension" in str(e) ): return None # Bug #569 / jedi#593 if ( isinstance(e, AttributeError) and "names_dict" in str(e) ): return None from jedi import debug debug_info = [] def _debug(level, str_out): if level == debug.NOTICE: prefix = "[N]" elif level == debug.WARNING: prefix = "[W]" else: prefix = "[?]" debug_info.append(u"{0} {1}".format(prefix, str_out)) jedi.set_debug_function(_debug, speed=False) try: script = jedi.Script(*args, **kwargs) return getattr(script, name)() except Exception as e: source = kwargs.get('source') sc_args = [] sc_args.extend(repr(arg) for arg in args) sc_args.extend("{0}={1}".format(k, "source" if k == "source" else repr(v)) for (k, v) in kwargs.items()) data = { "traceback": traceback.format_exc(), "jedi_debug_info": {'script_args': ", ".join(sc_args), 'source': source, 'method': name, 'debug_info': debug_info} } raise rpc.Fault(message=str(e), code=500, data=data) finally: jedi.set_debug_function(None)