k now... emacs>vim
This commit is contained in:
290
.emacs.d/elpa/elpy-20171206.847/elpy/ropebackend.py
Normal file
290
.emacs.d/elpa/elpy-20171206.847/elpy/ropebackend.py
Normal file
@@ -0,0 +1,290 @@
|
||||
"""Elpy backend using the Rope library.
|
||||
|
||||
This backend uses the Rope library:
|
||||
|
||||
http://rope.sourceforge.net/
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
import rope.contrib.codeassist
|
||||
import rope.base.project
|
||||
import rope.base.libutils
|
||||
import rope.base.exceptions
|
||||
import rope.contrib.findit
|
||||
|
||||
from elpy import rpc
|
||||
import elpy.pydocutils
|
||||
|
||||
VALIDATE_EVERY_SECONDS = 5
|
||||
MAXFIXES = 5
|
||||
|
||||
|
||||
class RopeBackend(object):
|
||||
"""The Rope backend class.
|
||||
|
||||
Implements the RPC calls we can pass on to Rope. Also subclasses
|
||||
the native backend to provide methods Rope does not provide, if
|
||||
any.
|
||||
|
||||
"""
|
||||
name = "rope"
|
||||
|
||||
def __init__(self, project_root):
|
||||
super(RopeBackend, self).__init__()
|
||||
self.last_validation = 0
|
||||
if not os.path.exists(project_root):
|
||||
raise rpc.Fault(
|
||||
"rope does not support files without a local project root",
|
||||
code=400
|
||||
)
|
||||
self.project_root = project_root
|
||||
self.completions = {}
|
||||
prefs = dict(ignored_resources=['*.pyc', '*~', '.ropeproject',
|
||||
'.hg', '.svn', '_svn', '.git'],
|
||||
python_files=['*.py'],
|
||||
save_objectdb=False,
|
||||
compress_objectdb=False,
|
||||
automatic_soa=True,
|
||||
soa_followed_calls=0,
|
||||
perform_doa=True,
|
||||
validate_objectdb=True,
|
||||
max_history_items=32,
|
||||
save_history=False,
|
||||
compress_history=False,
|
||||
indent_size=4,
|
||||
extension_modules=[],
|
||||
import_dynload_stdmods=True,
|
||||
ignore_syntax_errors=False,
|
||||
ignore_bad_imports=False)
|
||||
self.project = rope.base.project.Project(self.project_root,
|
||||
ropefolder=None,
|
||||
**prefs)
|
||||
|
||||
def get_resource(self, filename):
|
||||
if filename is not None and os.path.exists(filename):
|
||||
return rope.base.libutils.path_to_resource(self.project,
|
||||
filename,
|
||||
'file')
|
||||
else:
|
||||
return None
|
||||
|
||||
def validate(self):
|
||||
"""Validate the stored project.
|
||||
|
||||
This should be called before every use of Rope. It will
|
||||
revalidate the project, but do some call throttling.
|
||||
|
||||
"""
|
||||
now = time.time()
|
||||
if now > self.last_validation + VALIDATE_EVERY_SECONDS:
|
||||
try:
|
||||
self.project.validate()
|
||||
except rope.base.exceptions.ResourceNotFoundError:
|
||||
pass
|
||||
self.last_validation = now
|
||||
|
||||
def call_rope(self, rope_function, filename, source, offset,
|
||||
**kwargs):
|
||||
self.validate()
|
||||
resource = self.get_resource(filename)
|
||||
try:
|
||||
return rope_function(self.project,
|
||||
source, offset,
|
||||
resource,
|
||||
maxfixes=MAXFIXES,
|
||||
**kwargs)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def rpc_get_completions(self, filename, source, offset):
|
||||
proposals = self.call_rope(
|
||||
rope.contrib.codeassist.code_assist,
|
||||
filename, source, offset
|
||||
)
|
||||
if proposals is None:
|
||||
return []
|
||||
try:
|
||||
starting_offset = rope.contrib.codeassist.starting_offset(source,
|
||||
offset)
|
||||
except Exception:
|
||||
return []
|
||||
prefixlen = offset - starting_offset
|
||||
try:
|
||||
self.completions = dict((proposal.name, proposal)
|
||||
for proposal in proposals)
|
||||
return [{'name': proposal.name,
|
||||
'suffix': proposal.name[prefixlen:],
|
||||
'annotation': proposal.type,
|
||||
'meta': str(proposal)}
|
||||
for proposal in proposals]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def rpc_get_completion_docstring(self, completion):
|
||||
proposal = self.completions.get(completion)
|
||||
if proposal is None:
|
||||
return None
|
||||
else:
|
||||
return proposal.get_doc()
|
||||
|
||||
def rpc_get_completion_location(self, completion):
|
||||
proposal = self.completions.get(completion)
|
||||
if proposal is None:
|
||||
return None
|
||||
else:
|
||||
if not proposal.pyname:
|
||||
return None
|
||||
module, lineno = proposal.pyname.get_definition_location()
|
||||
if module is None:
|
||||
return None
|
||||
resource = module.get_module().get_resource()
|
||||
return (resource.real_path, lineno)
|
||||
|
||||
def rpc_get_definition(self, filename, source, offset):
|
||||
location = self.call_rope(
|
||||
rope.contrib.findit.find_definition,
|
||||
filename, source, offset
|
||||
)
|
||||
if location is None:
|
||||
return None
|
||||
else:
|
||||
return (location.resource.real_path, location.offset)
|
||||
|
||||
def rpc_get_calltip(self, filename, source, offset):
|
||||
offset = find_called_name_offset(source, offset)
|
||||
if 0 < offset < len(source) and source[offset] == ')':
|
||||
offset -= 1
|
||||
|
||||
calltip = self.call_rope(
|
||||
rope.contrib.codeassist.get_calltip,
|
||||
filename, source, offset,
|
||||
remove_self=True
|
||||
)
|
||||
if calltip is None:
|
||||
return None
|
||||
|
||||
calltip = calltip.replace(".__init__(", "(")
|
||||
calltip = calltip.replace("(self)", "()")
|
||||
calltip = calltip.replace("(self, ", "(")
|
||||
# "elpy.tests.support.source_and_offset(source)"
|
||||
# =>
|
||||
# "support.source_and_offset(source)"
|
||||
try:
|
||||
openpos = calltip.index("(")
|
||||
period2 = calltip.rindex(".", 0, openpos)
|
||||
period1 = calltip.rindex(".", 0, period2)
|
||||
calltip = calltip[period1 + 1:]
|
||||
except ValueError:
|
||||
pass
|
||||
return calltip
|
||||
|
||||
def rpc_get_docstring(self, filename, source, offset):
|
||||
return self.call_rope(
|
||||
rope.contrib.codeassist.get_doc,
|
||||
filename, source, offset
|
||||
)
|
||||
|
||||
|
||||
def find_called_name_offset(source, orig_offset):
|
||||
"""Return the offset of a calling function.
|
||||
|
||||
This only approximates movement.
|
||||
|
||||
"""
|
||||
offset = min(orig_offset, len(source) - 1)
|
||||
paren_count = 0
|
||||
while True:
|
||||
if offset <= 1:
|
||||
return orig_offset
|
||||
elif source[offset] == '(':
|
||||
if paren_count == 0:
|
||||
return offset - 1
|
||||
else:
|
||||
paren_count -= 1
|
||||
elif source[offset] == ')':
|
||||
paren_count += 1
|
||||
offset -= 1
|
||||
|
||||
|
||||
##################################################################
|
||||
# A recurring problem in Rope for Elpy is that it searches the whole
|
||||
# project root for Python files. If the user edits a file in their
|
||||
# home directory, this can easily read a whole lot of files, making
|
||||
# Rope practically useless. We change the file finding algorithm here
|
||||
# to only recurse into directories with an __init__.py file in them.
|
||||
def find_source_folders(self, folder):
|
||||
for resource in folder.get_folders():
|
||||
if self._is_package(resource):
|
||||
return [folder]
|
||||
result = []
|
||||
for resource in folder.get_files():
|
||||
if resource.name.endswith('.py'):
|
||||
result.append(folder)
|
||||
break
|
||||
for resource in folder.get_folders():
|
||||
if self._is_package(resource):
|
||||
result.append(resource)
|
||||
return result
|
||||
|
||||
import rope.base.pycore
|
||||
rope.base.pycore.PyCore._find_source_folders = find_source_folders
|
||||
|
||||
|
||||
def get_files(self):
|
||||
if self.files is None:
|
||||
self.files = get_python_project_files(self.project)
|
||||
return self.files
|
||||
|
||||
rope.base.project._FileListCacher.get_files = get_files
|
||||
|
||||
|
||||
def get_python_project_files(project):
|
||||
for dirname, subdirs, files in os.walk(project.root.real_path):
|
||||
for filename in files:
|
||||
yield rope.base.libutils.path_to_resource(
|
||||
project, os.path.join(dirname, filename), 'file')
|
||||
subdirs[:] = [subdir for subdir in subdirs
|
||||
if os.path.exists(os.path.join(dirname, subdir,
|
||||
"__init__.py"))]
|
||||
|
||||
|
||||
##################################################################
|
||||
# Monkey patching a method in rope because it doesn't complete import
|
||||
# statements.
|
||||
|
||||
orig_code_completions = (rope.contrib.codeassist.
|
||||
_PythonCodeAssist._code_completions)
|
||||
|
||||
|
||||
def code_completions(self):
|
||||
proposals = get_import_completions(self)
|
||||
if proposals:
|
||||
return proposals
|
||||
else:
|
||||
return orig_code_completions(self)
|
||||
|
||||
|
||||
def get_import_completions(self):
|
||||
if not self.word_finder.is_import_statement(self.offset):
|
||||
return []
|
||||
modulename = self.word_finder.get_primary_at(self.offset)
|
||||
# Rope can handle modules in packages
|
||||
if "." in modulename:
|
||||
return []
|
||||
return dict((name, FakeProposal(name))
|
||||
for name in elpy.pydocutils.get_modules()
|
||||
if name.startswith(modulename))
|
||||
|
||||
|
||||
class FakeProposal(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.type = "mock"
|
||||
|
||||
def get_doc(self):
|
||||
return None
|
||||
|
||||
rope.contrib.codeassist._PythonCodeAssist._code_completions = code_completions
|
||||
Reference in New Issue
Block a user