homes/common/.local/lib/python2.7/site-packages/jedi/api.py

458 lines
17 KiB
Python
Raw Normal View History

"""
Jedi is an autocompletion library for Python. It offers additonal
services such as goto / get_definition / pydoc support /
get_in_function_call / related names.
To give you a simple exmple how you can use the jedi library,
here is an exmple for the autocompletion feature:
>>> import jedi
>>> source = '''import json; json.l'''
>>> script = jedi.Script(source, 1, 19, '')
>>> script
<jedi.api.Script at 0x7f6d40f3db90>
>>> completions = script.complete()
>>> completions
[<Completion: load>, <Completion: loads>]
>>> completions[0].complete
'oad'
>>> completions[0].word
'load'
As you see Jedi is pretty simple and allows you to concentrate
writing a good text editor, while still having very good IDE features
for Python.
"""
from __future__ import with_statement
__all__ = ['Script', 'NotFoundError', 'set_debug_function']
import re
import parsing
import dynamic
import imports
import evaluate
import modules
import debug
import settings
import keywords
import helpers
import builtin
import api_classes
from _compatibility import next, unicode
class NotFoundError(Exception):
""" A custom error to avoid catching the wrong exceptions """
pass
class Script(object):
"""
A Script is the base for a completion, goto or whatever call.
:param source: The source code of the current file
:type source: string
:param line: The line to complete in.
:type line: int
:param col: The column to complete in.
:type col: int
:param source_path: The path in the os, the current module is in.
:type source_path: string or None
:param source_encoding: encoding for decoding `source`, when it
is not a `unicode` object.
:type source_encoding: string
"""
def __init__(self, source, line, column, source_path,
source_encoding='utf-8'):
debug.reset_time()
try:
source = unicode(source, source_encoding, 'replace')
# Use 'replace' over 'ignore' to hold code structure.
except TypeError: # `source` is already a unicode object
pass
self.pos = line, column
self.module = modules.ModuleWithCursor(source_path, source=source,
position=self.pos)
self.source_path = source_path
debug.speed('init')
@property
def parser(self):
""" The lazy parser """
return self.module.parser
def complete(self):
"""
An auto completer for python files.
:return: list of Completion objects, sorted by name and __ comes last.
:rtype: list
"""
def follow_imports_if_possible(name):
# TODO remove this, or move to another place (not used)
par = name.parent
if isinstance(par, parsing.Import) and not \
isinstance(self.parser.user_stmt, parsing.Import):
new = imports.ImportPath(par).follow(is_goto=True)
# Only remove the old entry if a new one has been found.
#print par, new, par.parent
if new:
try:
return new
except AttributeError: # .name undefined
pass
return [name]
debug.speed('complete start')
path = self.module.get_path_until_cursor()
path, dot, like = self._get_completion_parts(path)
try:
scopes = list(self._prepare_goto(path, True))
except NotFoundError:
scopes = []
scope_generator = evaluate.get_names_for_scope(
self.parser.user_scope, self.pos)
completions = []
for scope, name_list in scope_generator:
for c in name_list:
completions.append((c, scope))
else:
completions = []
debug.dbg('possible scopes', scopes)
for s in scopes:
if s.isinstance(evaluate.Function):
names = s.get_magic_method_names()
else:
if isinstance(s, imports.ImportPath):
if like == 'import':
l = self.module.get_line(self.pos[0])[:self.pos[1]]
if not l.endswith('import import'):
continue
names = s.get_defined_names(on_import_stmt=True)
else:
names = s.get_defined_names()
for c in names:
completions.append((c, s))
if not dot: # named_params have no dots
call_def = self.get_in_function_call()
if call_def:
if not call_def.module.is_builtin():
for p in call_def.params:
completions.append((p.get_name(), p))
# Do the completion if there is no path before and no import stmt.
if (not scopes or not isinstance(scopes[0], imports.ImportPath)) \
and not path:
# add keywords
bs = builtin.Builtin.scope
completions += ((k, bs) for k in keywords.get_keywords(
all=True))
needs_dot = not dot and path
comps = []
for c, s in set(completions):
n = c.names[-1]
if settings.case_insensitive_completion \
and n.lower().startswith(like.lower()) \
or n.startswith(like):
if not evaluate.filter_private_variable(s,
self.parser.user_stmt, n):
new = api_classes.Completion(c, needs_dot,
len(like), s)
comps.append(new)
debug.speed('complete end')
return sorted(comps, key=lambda x: (x.word.startswith('__'),
x.word.startswith('_'),
x.word.lower()))
def _prepare_goto(self, goto_path, is_like_search=False):
""" Base for complete, goto and get_definition. Basically it returns
the resolved scopes under cursor. """
debug.dbg('start: %s in %s' % (goto_path, self.parser.scope))
user_stmt = self.parser.user_stmt
debug.speed('parsed')
if not user_stmt and len(goto_path.split('\n')) > 1:
# If the user_stmt is not defined and the goto_path is multi line,
# something's strange. Most probably the backwards tokenizer
# matched to much.
return []
if isinstance(user_stmt, parsing.Import):
scopes = [self._get_on_import_stmt(is_like_search)[0]]
else:
# just parse one statement, take it and evaluate it
stmt = self._get_under_cursor_stmt(goto_path)
scopes = evaluate.follow_statement(stmt)
return scopes
def _get_under_cursor_stmt(self, cursor_txt):
r = parsing.PyFuzzyParser(cursor_txt, self.source_path, no_docstr=True)
try:
stmt = r.module.statements[0]
except IndexError:
raise NotFoundError()
stmt.start_pos = self.pos
stmt.parent = self.parser.user_scope
return stmt
def get_definition(self):
"""
Returns the definitions of a the path under the cursor. This is
not a goto function! This follows complicated paths and returns the
end, not the first definition.
The big difference of goto and get_definition is that goto doesn't
follow imports and statements.
Multiple objects may be returned, because Python itself is a dynamic
language, which means depending on an option you can have two different
versions of a function.
:return: list of Definition objects, which are basically scopes.
:rtype: list
"""
def resolve_import_paths(scopes):
for s in scopes.copy():
if isinstance(s, imports.ImportPath):
scopes.remove(s)
scopes.update(resolve_import_paths(set(s.follow())))
return scopes
goto_path = self.module.get_path_under_cursor()
context = self.module.get_context()
if next(context) in ('class', 'def'):
scopes = set([self.module.parser.user_scope])
elif not goto_path:
op = self.module.get_operator_under_cursor()
scopes = set([keywords.get_operator(op, self.pos)] if op else [])
else:
scopes = set(self._prepare_goto(goto_path))
scopes = resolve_import_paths(scopes)
# add keywords
scopes |= keywords.get_keywords(string=goto_path, pos=self.pos)
d = set([api_classes.Definition(s) for s in scopes
if not isinstance(s, imports.ImportPath._GlobalNamespace)])
return sorted(d, key=lambda x: (x.module_path, x.start_pos))
def goto(self):
"""
Returns the first definition found by goto. This means: It doesn't
follow imports and statements.
Multiple objects may be returned, because Python itself is a dynamic
language, which means depending on an option you can have two different
versions of a function.
:return: list of Definition objects, which are basically scopes.
"""
d = [api_classes.Definition(d) for d in set(self._goto()[0])]
return sorted(d, key=lambda x: (x.module_path, x.start_pos))
def _goto(self, add_import_name=False):
"""
Used for goto and related_names.
:param add_import_name: TODO add description
"""
def follow_inexistent_imports(defs):
""" Imports can be generated, e.g. following
`multiprocessing.dummy` generates an import dummy in the
multiprocessing module. The Import doesn't exist -> follow.
"""
definitions = set(defs)
for d in defs:
if isinstance(d.parent, parsing.Import) \
and d.start_pos == (0, 0):
i = imports.ImportPath(d.parent).follow(is_goto=True)
definitions.remove(d)
definitions |= follow_inexistent_imports(i)
return definitions
goto_path = self.module.get_path_under_cursor()
context = self.module.get_context()
if next(context) in ('class', 'def'):
user_scope = self.parser.user_scope
definitions = set([user_scope.name])
search_name = str(user_scope.name)
elif isinstance(self.parser.user_stmt, parsing.Import):
s, name_part = self._get_on_import_stmt()
try:
definitions = [s.follow(is_goto=True)[0]]
except IndexError:
definitions = []
search_name = str(name_part)
if add_import_name:
import_name = self.parser.user_stmt.get_defined_names()
# imports have only one name
if name_part == import_name[0].names[-1]:
definitions.append(import_name[0])
else:
stmt = self._get_under_cursor_stmt(goto_path)
defs, search_name = evaluate.goto(stmt)
definitions = follow_inexistent_imports(defs)
return definitions, search_name
def related_names(self, additional_module_paths=[]):
"""
Returns `dynamic.RelatedName` objects, which contain all names, that
are defined by the same variable, function, class or import.
This function can be used either to show all the usages of a variable
or for renaming purposes.
TODO implement additional_module_paths
"""
user_stmt = self.parser.user_stmt
definitions, search_name = self._goto(add_import_name=True)
if isinstance(user_stmt, parsing.Statement) \
and self.pos < user_stmt.get_assignment_calls().start_pos:
# the search_name might be before `=`
definitions = [v for v in user_stmt.set_vars
if str(v) == search_name]
if not isinstance(user_stmt, parsing.Import):
# import case is looked at with add_import_name option
definitions = dynamic.related_name_add_import_modules(definitions,
search_name)
module = set([d.get_parent_until() for d in definitions])
module.add(self.parser.module)
names = dynamic.related_names(definitions, search_name, module)
for d in set(definitions):
if isinstance(d, parsing.Module):
names.append(api_classes.RelatedName(d, d))
else:
names.append(api_classes.RelatedName(d.names[0], d))
return sorted(set(names), key=lambda x: (x.module_path, x.start_pos),
reverse=True)
def get_in_function_call(self):
"""
Return the function, that the cursor is in, e.g.:
>>> isinstance(| # | <-- cursor is here
This would return the `isinstance` function. In contrary:
>>> isinstance()| # | <-- cursor is here
This would return `None`.
"""
def check_user_stmt(user_stmt):
if user_stmt is None \
or not isinstance(user_stmt, parsing.Statement):
return None, 0
ass = helpers.fast_parent_copy(user_stmt.get_assignment_calls())
call, index, stop = helpers.scan_array_for_pos(ass, self.pos)
return call, index
def check_cache():
""" Do the parsing with a part parser, therefore reduce ressource
costs.
TODO this is not working with multi-line docstrings, improve.
"""
if self.source_path is None:
return None, 0
try:
timestamp, parser = builtin.CachedModule.cache[
self.source_path]
except KeyError:
return None, 0
part_parser = self.module.get_part_parser()
user_stmt = part_parser.user_stmt
call, index = check_user_stmt(user_stmt)
if call:
old_stmt = parser.module.get_statement_for_position(self.pos)
if old_stmt is None:
return None, 0
old_call, old_index = check_user_stmt(old_stmt)
if old_call:
# compare repr because that should definitely be the same.
# Otherwise the whole thing is out of sync.
if repr(old_call) == repr(call):
# return the index of the part_parser
return old_call, index
return None, 0
else:
raise NotFoundError()
debug.speed('func_call start')
try:
call, index = check_cache()
except NotFoundError:
return None
debug.speed('func_call parsed')
if call is None:
# This is a backup, if the above is not successful.
user_stmt = self.parser.user_stmt
call, index = check_user_stmt(user_stmt)
if call is None:
return None
debug.speed('func_call user_stmt')
with helpers.scale_speed_settings(settings.scale_get_in_function_call):
origins = evaluate.follow_call(call)
debug.speed('func_call followed')
if len(origins) == 0:
return None
# just take entry zero, because we need just one.
executable = origins[0]
return api_classes.CallDef(executable, index, call)
def _get_on_import_stmt(self, is_like_search=False):
""" Resolve the user statement, if it is an import. Only resolve the
parts until the user position. """
user_stmt = self.parser.user_stmt
import_names = user_stmt.get_all_import_names()
kill_count = -1
cur_name_part = None
for i in import_names:
if user_stmt.alias == i:
continue
for name_part in i.names:
if name_part.end_pos >= self.pos:
if not cur_name_part:
cur_name_part = name_part
kill_count += 1
i = imports.ImportPath(user_stmt, is_like_search,
kill_count=kill_count, direct_resolve=True)
return i, cur_name_part
def _get_completion_parts(self, path):
"""
Returns the parts for the completion
:return: tuple - (path, dot, like)
"""
match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path, flags=re.S)
return match.groups()
def __del__(self):
evaluate.clear_caches()
def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
notices=True, speed=True):
"""
You can define a callback debug function to get all the debug messages.
:param func_cb: The callback function for debug messages, with n params.
"""
debug.debug_function = func_cb
debug.enable_warning = warnings
debug.enable_notice = notices
debug.enable_speed = speed