Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

367 rivejä
13 KiB

"""Elpy backend using the Jedi library.
This backend uses the Jedi library:
https://github.com/davidhalter/jedi
"""
import sys
import traceback
import re
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)
locations = run_with_debug(jedi, 'goto_definitions',
source=source, line=line, column=column,
path=filename, encoding='utf-8')
if locations and locations[-1].docstring():
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)
locations = run_with_debug(jedi, 'goto_definitions',
source=source, line=line, column=column,
path=filename, encoding='utf-8')
# 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
or locations[0].module_name == 'builtins'
or locations[0].module_name == '__builtin__')
):
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_assignment(self, filename, source, offset):
line, column = pos_to_linecol(source, offset)
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
# Strip 'param' added by jedi at the beggining of
# parameter names. Should be unecessary for jedi > 0.13.0
params = [re.sub("^param ", '', param.description)
for param in call.params]
return {"name": call.name,
"index": call.index,
"params": params}
def rpc_get_oneline_docstring(self, filename, source, offset):
"""Return a oneline docstring for the symbol at offset"""
line, column = pos_to_linecol(source, offset)
definitions = run_with_debug(jedi, 'goto_definitions',
source=source, line=line, column=column,
path=filename, encoding='utf-8')
assignments = run_with_debug(jedi, 'goto_assignments',
source=source, line=line, column=column,
path=filename, encoding='utf-8')
if definitions:
definition = definitions[0]
else:
definition = None
if assignments:
assignment = assignments[0]
else:
assignment = None
if definition:
# Get name
if definition.type in ['function', 'class']:
raw_name = definition.name
name = '{}()'.format(raw_name)
doc = definition.docstring().split('\n')
elif definition.type in ['module']:
raw_name = definition.name
name = '{} {}'.format(raw_name, definition.type)
doc = definition.docstring().split('\n')
elif (definition.type in ['instance']
and hasattr(assignment, "name")):
raw_name = assignment.name
name = raw_name
doc = assignment.docstring().split('\n')
else:
return None
# Keep only the first paragraph that is not a function declaration
lines = []
call = "{}(".format(raw_name)
# last line
doc.append('')
for i in range(len(doc)):
if doc[i] == '' and len(lines) != 0:
paragraph = " ".join(lines)
lines = []
if call != paragraph[0:len(call)]:
break
paragraph = ""
continue
lines.append(doc[i])
# Keep only the first sentence
onelinedoc = paragraph.split('. ', 1)
if len(onelinedoc) == 2:
onelinedoc = onelinedoc[0] + '.'
else:
onelinedoc = onelinedoc[0]
if onelinedoc == '':
onelinedoc = "No documentation"
return {"name": name,
"doc": onelinedoc}
return None
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)
uses = run_with_debug(jedi, 'usages',
source=source, line=line, column=column,
path=filename, encoding='utf-8')
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
def rpc_get_names(self, filename, source, offset):
"""Return the list of possible names"""
names = jedi.api.names(source=source,
path=filename, encoding='utf-8',
all_scopes=True,
definitions=True,
references=True)
result = []
for name in names:
if name.module_path == filename:
offset = linecol_to_pos(source, name.line, name.column)
elif name.module_path is not None:
with open(name.module_path) as f:
text = f.read()
offset = linecol_to_pos(text, name.line, name.column)
result.append({"name": name.name,
"filename": name.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', ())
try:
script = jedi.Script(*args, **kwargs)
return getattr(script, name)()
except Exception as e:
if isinstance(e, re_raise):
raise
# 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
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)