forked from VimPlug/jedi
Merge branch 'dev' into get_code_fidelity
Conflicts: jedi/evaluate/docstrings.py jedi/parser/representation.py Resolving merge problems: * Introducing docstring for compiled.fake * Partly fixing poor decision in TokenDocstring __init__
This commit is contained in:
751
jedi/api/__init__.py
Normal file
751
jedi/api/__init__.py
Normal file
@@ -0,0 +1,751 @@
|
||||
"""
|
||||
The API basically only provides one class. You can create a :class:`Script` and
|
||||
use its methods.
|
||||
|
||||
Additionally you can add a debug function with :func:`set_debug_function` and
|
||||
catch :exc:`NotFoundError` which is being raised if your completion is not
|
||||
possible.
|
||||
|
||||
.. warning:: Please, note that Jedi is **not thread safe**.
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
import warnings
|
||||
from itertools import chain
|
||||
|
||||
from jedi._compatibility import next, unicode, builtins
|
||||
from jedi.parser import Parser
|
||||
from jedi.parser import representation as pr
|
||||
from jedi.parser.user_context import UserContext, UserContextParser
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi import common
|
||||
from jedi import cache
|
||||
from jedi.api import keywords
|
||||
from jedi.api import classes
|
||||
from jedi.api import interpreter
|
||||
from jedi.evaluate import Evaluator, filter_private_variable
|
||||
from jedi.evaluate import representation as er
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate.finder import get_names_of_scope
|
||||
|
||||
|
||||
class NotFoundError(Exception):
|
||||
"""A custom error to avoid catching the wrong exceptions."""
|
||||
|
||||
|
||||
class Script(object):
|
||||
"""
|
||||
A Script is the base for completions, goto or whatever you want to do with
|
||||
|jedi|.
|
||||
|
||||
You can either use the ``source`` parameter or ``path`` to read a file.
|
||||
Usually you're going to want to use both of them (in an editor).
|
||||
|
||||
:param source: The source code of the current file, separated by newlines.
|
||||
:type source: str
|
||||
:param line: The line to perform actions on (starting with 1).
|
||||
:type line: int
|
||||
:param col: The column of the cursor (starting with 0).
|
||||
:type col: int
|
||||
:param path: The path of the file in the file system, or ``''`` if
|
||||
it hasn't been saved yet.
|
||||
:type path: str or None
|
||||
:param encoding: The encoding of ``source``, if it is not a
|
||||
``unicode`` object (default ``'utf-8'``).
|
||||
:type encoding: str
|
||||
"""
|
||||
def __init__(self, source=None, line=None, column=None, path=None,
|
||||
encoding='utf-8', source_path=None):
|
||||
if source_path is not None:
|
||||
warnings.warn("Use path instead of source_path.", DeprecationWarning)
|
||||
path = source_path
|
||||
self._source_path = path
|
||||
self.path = None if path is None else os.path.abspath(path)
|
||||
|
||||
if source is None:
|
||||
with open(path) as f:
|
||||
source = f.read()
|
||||
|
||||
lines = source.splitlines() or ['']
|
||||
if source and source[-1] == '\n':
|
||||
lines.append('')
|
||||
line = max(len(lines), 1) if line is None else line
|
||||
if not (0 < line <= len(lines)):
|
||||
raise ValueError('`line` parameter is not in a valid range.')
|
||||
|
||||
line_len = len(lines[line - 1])
|
||||
column = line_len if column is None else column
|
||||
if not (0 <= column <= line_len):
|
||||
raise ValueError('`column` parameter is not in a valid range.')
|
||||
self._pos = line, column
|
||||
|
||||
classes.clear_caches()
|
||||
debug.reset_time()
|
||||
self.source = common.source_to_unicode(source, encoding)
|
||||
self._user_context = UserContext(self.source, self._pos)
|
||||
self._parser = UserContextParser(self.source, path, self._pos, self._user_context)
|
||||
self._evaluator = Evaluator()
|
||||
debug.speed('init')
|
||||
|
||||
@property
|
||||
def source_path(self):
|
||||
"""
|
||||
.. deprecated:: 0.7.0
|
||||
Use :attr:`.path` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
warnings.warn("Use path instead of source_path.", DeprecationWarning)
|
||||
return self.path
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, repr(self._source_path))
|
||||
|
||||
def completions(self):
|
||||
"""
|
||||
Return :class:`classes.Completion` objects. Those objects contain
|
||||
information about the completions, more than just names.
|
||||
|
||||
:return: Completion objects, sorted by name and __ comes last.
|
||||
:rtype: list of :class:`classes.Completion`
|
||||
"""
|
||||
def get_completions(user_stmt, bs):
|
||||
if isinstance(user_stmt, pr.Import):
|
||||
context = self._user_context.get_context()
|
||||
next(context) # skip the path
|
||||
if next(context) == 'from':
|
||||
# completion is just "import" if before stands from ..
|
||||
return ((k, bs) for k in keywords.keyword_names('import'))
|
||||
return self._simple_complete(path, like)
|
||||
|
||||
debug.speed('completions start')
|
||||
path = self._user_context.get_path_until_cursor()
|
||||
if re.search('^\.|\.\.$', path):
|
||||
return []
|
||||
path, dot, like = self._get_completion_parts()
|
||||
|
||||
user_stmt = self._parser.user_stmt(True)
|
||||
b = compiled.builtin
|
||||
completions = get_completions(user_stmt, b)
|
||||
|
||||
if not dot:
|
||||
# add named params
|
||||
for call_def in self.call_signatures():
|
||||
if not isinstance(call_def.module, compiled.CompiledObject):
|
||||
for p in call_def.params:
|
||||
completions.append((p.get_name(), p))
|
||||
|
||||
if not path and not isinstance(user_stmt, pr.Import):
|
||||
# add keywords
|
||||
completions += ((k, b) for k in keywords.keyword_names(all=True))
|
||||
|
||||
needs_dot = not dot and path
|
||||
|
||||
comps = []
|
||||
comp_dct = {}
|
||||
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 filter_private_variable(s, user_stmt or self._parser.user_scope(), n):
|
||||
new = classes.Completion(self._evaluator, c, needs_dot, len(like), s)
|
||||
k = (new.name, new.complete) # key
|
||||
if k in comp_dct and settings.no_completion_duplicates:
|
||||
comp_dct[k]._same_name_completions.append(new)
|
||||
else:
|
||||
comp_dct[k] = new
|
||||
comps.append(new)
|
||||
|
||||
debug.speed('completions end')
|
||||
|
||||
return sorted(comps, key=lambda x: (x.name.startswith('__'),
|
||||
x.name.startswith('_'),
|
||||
x.name.lower()))
|
||||
|
||||
def _simple_complete(self, path, like):
|
||||
try:
|
||||
scopes = list(self._prepare_goto(path, True))
|
||||
except NotFoundError:
|
||||
scopes = []
|
||||
scope_generator = get_names_of_scope(self._evaluator,
|
||||
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 completion scopes: %s', scopes)
|
||||
for s in scopes:
|
||||
if s.isinstance(er.Function):
|
||||
names = s.get_magic_function_names()
|
||||
else:
|
||||
if isinstance(s, imports.ImportPath):
|
||||
under = like + self._user_context.get_path_after_cursor()
|
||||
if under == 'import':
|
||||
current_line = self._user_context.get_position_line()
|
||||
if not current_line.endswith('import import'):
|
||||
continue
|
||||
a = s.import_stmt.alias
|
||||
if a and a.start_pos <= self._pos <= a.end_pos:
|
||||
continue
|
||||
names = s.get_defined_names(on_import_stmt=True)
|
||||
else:
|
||||
names = s.get_defined_names()
|
||||
|
||||
for c in names:
|
||||
completions.append((c, s))
|
||||
return completions
|
||||
|
||||
def _prepare_goto(self, goto_path, is_completion=False):
|
||||
"""
|
||||
Base for completions/goto. Basically it returns the resolved scopes
|
||||
under cursor.
|
||||
"""
|
||||
debug.dbg('start: %s in %s', goto_path, self._parser.user_scope())
|
||||
|
||||
user_stmt = self._parser.user_stmt(is_completion)
|
||||
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, pr.Import):
|
||||
scopes = [self._get_on_import_stmt(user_stmt, is_completion)[0]]
|
||||
else:
|
||||
# just parse one statement, take it and evaluate it
|
||||
stmt = self._get_under_cursor_stmt(goto_path)
|
||||
scopes = self._evaluator.eval_statement(stmt)
|
||||
return scopes
|
||||
|
||||
def _get_under_cursor_stmt(self, cursor_txt):
|
||||
offset = self._pos[0] - 1, self._pos[1]
|
||||
r = Parser(cursor_txt, no_docstr=True, offset=offset)
|
||||
try:
|
||||
stmt = r.module.statements[0]
|
||||
except IndexError:
|
||||
raise NotFoundError()
|
||||
stmt.parent = self._parser.user_scope()
|
||||
return stmt
|
||||
|
||||
def complete(self):
|
||||
"""
|
||||
.. deprecated:: 0.6.0
|
||||
Use :attr:`.completions` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
warnings.warn("Use completions instead.", DeprecationWarning)
|
||||
return self.completions()
|
||||
|
||||
def goto(self):
|
||||
"""
|
||||
.. deprecated:: 0.6.0
|
||||
Use :attr:`.goto_assignments` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
warnings.warn("Use goto_assignments instead.", DeprecationWarning)
|
||||
return self.goto_assignments()
|
||||
|
||||
def definition(self):
|
||||
"""
|
||||
.. deprecated:: 0.6.0
|
||||
Use :attr:`.goto_definitions` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
warnings.warn("Use goto_definitions instead.", DeprecationWarning)
|
||||
return self.goto_definitions()
|
||||
|
||||
def get_definition(self):
|
||||
"""
|
||||
.. deprecated:: 0.5.0
|
||||
Use :attr:`.goto_definitions` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
warnings.warn("Use goto_definitions instead.", DeprecationWarning)
|
||||
return self.goto_definitions()
|
||||
|
||||
def related_names(self):
|
||||
"""
|
||||
.. deprecated:: 0.6.0
|
||||
Use :attr:`.usages` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
warnings.warn("Use usages instead.", DeprecationWarning)
|
||||
return self.usages()
|
||||
|
||||
def get_in_function_call(self):
|
||||
"""
|
||||
.. deprecated:: 0.6.0
|
||||
Use :attr:`.call_signatures` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
return self.function_definition()
|
||||
|
||||
def function_definition(self):
|
||||
"""
|
||||
.. deprecated:: 0.6.0
|
||||
Use :attr:`.call_signatures` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
warnings.warn("Use line instead.", DeprecationWarning)
|
||||
sig = self.call_signatures()
|
||||
return sig[0] if sig else None
|
||||
|
||||
def goto_definitions(self):
|
||||
"""
|
||||
Return the definitions of a the path under the cursor. goto function!
|
||||
This follows complicated paths and returns the end, not the first
|
||||
definition. The big difference between :meth:`goto_assignments` and
|
||||
:meth:`goto_definitions` is that :meth:`goto_assignments` 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.
|
||||
|
||||
:rtype: list of :class:`classes.Definition`
|
||||
"""
|
||||
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._user_context.get_path_under_cursor()
|
||||
|
||||
context = self._user_context.get_context()
|
||||
scopes = set()
|
||||
lower_priority_operators = ('()', '(', ',')
|
||||
"""Operators that could hide callee."""
|
||||
if next(context) in ('class', 'def'):
|
||||
scopes = set([self._parser.user_scope()])
|
||||
elif not goto_path:
|
||||
op = self._user_context.get_operator_under_cursor()
|
||||
if op and op not in lower_priority_operators:
|
||||
scopes = set([keywords.get_operator(op, self._pos)])
|
||||
|
||||
# Fetch definition of callee, if there's no path otherwise.
|
||||
if not goto_path:
|
||||
(call, _) = self._func_call_and_param_index()
|
||||
if call is not None:
|
||||
while call.next is not None:
|
||||
call = call.next
|
||||
# reset cursor position:
|
||||
(row, col) = call.name.end_pos
|
||||
pos = (row, max(col - 1, 0))
|
||||
self._user_context = UserContext(self.source, pos)
|
||||
# then try to find the path again
|
||||
goto_path = self._user_context.get_path_under_cursor()
|
||||
|
||||
if not scopes:
|
||||
if goto_path:
|
||||
scopes = set(self._prepare_goto(goto_path))
|
||||
elif op in lower_priority_operators:
|
||||
scopes = set([keywords.get_operator(op, self._pos)])
|
||||
|
||||
scopes = resolve_import_paths(scopes)
|
||||
|
||||
# add keywords
|
||||
scopes |= keywords.keywords(string=goto_path, pos=self._pos)
|
||||
|
||||
d = set([classes.Definition(self._evaluator, s) for s in scopes
|
||||
if s is not imports.ImportPath.GlobalNamespace])
|
||||
return self._sorted_defs(d)
|
||||
|
||||
def goto_assignments(self):
|
||||
"""
|
||||
Return the first definition found. Imports and statements aren't
|
||||
followed. 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.
|
||||
|
||||
:rtype: list of :class:`classes.Definition`
|
||||
"""
|
||||
results, _ = self._goto()
|
||||
d = [classes.Definition(self._evaluator, d) for d in set(results)
|
||||
if d is not imports.ImportPath.GlobalNamespace]
|
||||
return self._sorted_defs(d)
|
||||
|
||||
def _goto(self, add_import_name=False):
|
||||
"""
|
||||
Used for goto_assignments and usages.
|
||||
|
||||
:param add_import_name: Add the the name (if import) to the result.
|
||||
"""
|
||||
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, pr.Import) \
|
||||
and d.start_pos == (0, 0):
|
||||
i = imports.ImportPath(self._evaluator, d.parent).follow(is_goto=True)
|
||||
definitions.remove(d)
|
||||
definitions |= follow_inexistent_imports(i)
|
||||
return definitions
|
||||
|
||||
goto_path = self._user_context.get_path_under_cursor()
|
||||
context = self._user_context.get_context()
|
||||
user_stmt = self._parser.user_stmt()
|
||||
if next(context) in ('class', 'def'):
|
||||
user_scope = self._parser.user_scope()
|
||||
definitions = set([user_scope.name])
|
||||
search_name = unicode(user_scope.name)
|
||||
elif isinstance(user_stmt, pr.Import):
|
||||
s, name_part = self._get_on_import_stmt(user_stmt)
|
||||
try:
|
||||
definitions = [s.follow(is_goto=True)[0]]
|
||||
except IndexError:
|
||||
definitions = []
|
||||
search_name = unicode(name_part)
|
||||
|
||||
if add_import_name:
|
||||
import_name = user_stmt.get_defined_names()
|
||||
# imports have only one name
|
||||
if not user_stmt.star \
|
||||
and name_part == import_name[0].names[-1]:
|
||||
definitions.append(import_name[0])
|
||||
else:
|
||||
stmt = self._get_under_cursor_stmt(goto_path)
|
||||
defs, search_name = self._evaluator.goto(stmt)
|
||||
definitions = follow_inexistent_imports(defs)
|
||||
if isinstance(user_stmt, pr.Statement):
|
||||
c = user_stmt.expression_list()
|
||||
if c and not isinstance(c[0], (str, unicode)) \
|
||||
and c[0].start_pos > self._pos \
|
||||
and not re.search(r'\.\w+$', goto_path):
|
||||
# The cursor must be after the start, otherwise the
|
||||
# statement is just an assignee.
|
||||
definitions = [user_stmt]
|
||||
return definitions, search_name
|
||||
|
||||
def usages(self, additional_module_paths=()):
|
||||
"""
|
||||
Return :class:`classes.Usage` objects, which contain all
|
||||
names that point to the definition of the name under the cursor. This
|
||||
is very useful for refactoring (renaming), or to show all usages of a
|
||||
variable.
|
||||
|
||||
.. todo:: Implement additional_module_paths
|
||||
|
||||
:rtype: list of :class:`classes.Usage`
|
||||
"""
|
||||
temp, settings.dynamic_flow_information = \
|
||||
settings.dynamic_flow_information, False
|
||||
user_stmt = self._parser.user_stmt()
|
||||
definitions, search_name = self._goto(add_import_name=True)
|
||||
if isinstance(user_stmt, pr.Statement):
|
||||
c = user_stmt.expression_list()[0]
|
||||
if not isinstance(c, unicode) and self._pos < c.start_pos:
|
||||
# the search_name might be before `=`
|
||||
definitions = [v for v in user_stmt.get_set_vars()
|
||||
if unicode(v.names[-1]) == search_name]
|
||||
if not isinstance(user_stmt, pr.Import):
|
||||
# import case is looked at with add_import_name option
|
||||
definitions = usages_add_import_modules(self._evaluator, definitions, search_name)
|
||||
|
||||
module = set([d.get_parent_until() for d in definitions])
|
||||
module.add(self._parser.module())
|
||||
names = usages(self._evaluator, definitions, search_name, module)
|
||||
|
||||
for d in set(definitions):
|
||||
if isinstance(d, pr.Module):
|
||||
names.append(classes.Usage(self._evaluator, d, d))
|
||||
elif isinstance(d, er.Instance):
|
||||
# Instances can be ignored, because they are being created by
|
||||
# ``__getattr__``.
|
||||
pass
|
||||
else:
|
||||
names.append(classes.Usage(self._evaluator, d.names[-1], d))
|
||||
|
||||
settings.dynamic_flow_information = temp
|
||||
return self._sorted_defs(set(names))
|
||||
|
||||
def call_signatures(self):
|
||||
"""
|
||||
Return the function object of the call you're currently in.
|
||||
|
||||
E.g. if the cursor is here::
|
||||
|
||||
abs(# <-- cursor is here
|
||||
|
||||
This would return the ``abs`` function. On the other hand::
|
||||
|
||||
abs()# <-- cursor is here
|
||||
|
||||
This would return ``None``.
|
||||
|
||||
:rtype: list of :class:`classes.CallDef`
|
||||
"""
|
||||
|
||||
call, index = self._func_call_and_param_index()
|
||||
if call is None:
|
||||
return []
|
||||
|
||||
user_stmt = self._parser.user_stmt()
|
||||
with common.scale_speed_settings(settings.scale_call_signatures):
|
||||
_callable = lambda: self._evaluator.eval_call(call)
|
||||
origins = cache.cache_call_signatures(_callable, user_stmt)
|
||||
debug.speed('func_call followed')
|
||||
|
||||
return [classes.CallDef(o, index, call) for o in origins
|
||||
if o.isinstance(er.Function, er.Instance, er.Class)
|
||||
or isinstance(o, compiled.CompiledObject) and o.type() != 'module']
|
||||
|
||||
def _func_call_and_param_index(self):
|
||||
debug.speed('func_call start')
|
||||
call, index = None, 0
|
||||
if call is None:
|
||||
user_stmt = self._parser.user_stmt()
|
||||
if user_stmt is not None and isinstance(user_stmt, pr.Statement):
|
||||
call, index, _ = helpers.search_call_signatures(user_stmt, self._pos)
|
||||
debug.speed('func_call parsed')
|
||||
return call, index
|
||||
|
||||
def _get_on_import_stmt(self, user_stmt, is_like_search=False):
|
||||
""" Resolve the user statement, if it is an import. Only resolve the
|
||||
parts until the user position. """
|
||||
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
|
||||
|
||||
context = self._user_context.get_context()
|
||||
just_from = next(context) == 'from'
|
||||
|
||||
i = imports.ImportPath(self._evaluator, user_stmt, is_like_search,
|
||||
kill_count=kill_count, direct_resolve=True,
|
||||
is_just_from=just_from)
|
||||
return i, cur_name_part
|
||||
|
||||
def _get_completion_parts(self):
|
||||
"""
|
||||
Returns the parts for the completion
|
||||
:return: tuple - (path, dot, like)
|
||||
"""
|
||||
path = self._user_context.get_path_until_cursor()
|
||||
match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path, flags=re.S)
|
||||
return match.groups()
|
||||
|
||||
def _sorted_defs(self, d):
|
||||
# Note: `or ''` below is required because `module_path` could be
|
||||
# None and you can't compare None and str in Python 3.
|
||||
return sorted(d, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0))
|
||||
|
||||
|
||||
class Interpreter(Script):
|
||||
"""
|
||||
Jedi API for Python REPLs.
|
||||
|
||||
In addition to completion of simple attribute access, Jedi
|
||||
supports code completion based on static code analysis.
|
||||
Jedi can complete attributes of object which is not initialized
|
||||
yet.
|
||||
|
||||
>>> from os.path import join
|
||||
>>> namespace = locals()
|
||||
>>> script = Interpreter('join().up', [namespace])
|
||||
>>> print(script.completions()[0].name)
|
||||
upper
|
||||
"""
|
||||
|
||||
def __init__(self, source, namespaces=[], **kwds):
|
||||
"""
|
||||
Parse `source` and mixin interpreted Python objects from `namespaces`.
|
||||
|
||||
:type source: str
|
||||
:arg source: Code to parse.
|
||||
:type namespaces: list of dict
|
||||
:arg namespaces: a list of namespace dictionaries such as the one
|
||||
returned by :func:`locals`.
|
||||
|
||||
Other optional arguments are same as the ones for :class:`Script`.
|
||||
If `line` and `column` are None, they are assumed be at the end of
|
||||
`source`.
|
||||
"""
|
||||
super(Interpreter, self).__init__(source, **kwds)
|
||||
self.namespaces = namespaces
|
||||
|
||||
# Here we add the namespaces to the current parser.
|
||||
interpreter.create(self._evaluator, namespaces[0], self._parser.module())
|
||||
|
||||
def _simple_complete(self, path, like):
|
||||
user_stmt = self._parser.user_stmt(True)
|
||||
is_simple_path = not path or re.search('^[\w][\w\d.]*$', path)
|
||||
if isinstance(user_stmt, pr.Import) or not is_simple_path:
|
||||
return super(type(self), self)._simple_complete(path, like)
|
||||
else:
|
||||
class NamespaceModule(object):
|
||||
def __getattr__(_, name):
|
||||
for n in self.namespaces:
|
||||
try:
|
||||
return n[name]
|
||||
except KeyError:
|
||||
pass
|
||||
raise AttributeError()
|
||||
|
||||
def __dir__(_):
|
||||
return list(set(chain.from_iterable(n.keys()
|
||||
for n in self.namespaces)))
|
||||
|
||||
paths = path.split('.') if path else []
|
||||
|
||||
namespaces = (NamespaceModule(), builtins)
|
||||
for p in paths:
|
||||
old, namespaces = namespaces, []
|
||||
for n in old:
|
||||
try:
|
||||
namespaces.append(getattr(n, p))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
completions = []
|
||||
for namespace in namespaces:
|
||||
for name in dir(namespace):
|
||||
if name.lower().startswith(like.lower()):
|
||||
scope = self._parser.module()
|
||||
n = helpers.FakeName(name, scope)
|
||||
completions.append((n, scope))
|
||||
return completions
|
||||
|
||||
|
||||
def defined_names(source, path=None, encoding='utf-8'):
|
||||
"""
|
||||
Get all definitions in `source` sorted by its position.
|
||||
|
||||
This functions can be used for listing functions, classes and
|
||||
data defined in a file. This can be useful if you want to list
|
||||
them in "sidebar". Each element in the returned list also has
|
||||
`defined_names` method which can be used to get sub-definitions
|
||||
(e.g., methods in class).
|
||||
|
||||
:rtype: list of classes.Definition
|
||||
"""
|
||||
parser = Parser(
|
||||
common.source_to_unicode(source, encoding),
|
||||
module_path=path,
|
||||
)
|
||||
return classes.defined_names(Evaluator(), parser.module)
|
||||
|
||||
|
||||
def preload_module(*modules):
|
||||
"""
|
||||
Preloading modules tells Jedi to load a module now, instead of lazy parsing
|
||||
of modules. Usful for IDEs, to control which modules to load on startup.
|
||||
|
||||
:param modules: different module names, list of string.
|
||||
"""
|
||||
for m in modules:
|
||||
s = "import %s as x; x." % m
|
||||
Script(s, 1, len(s), None).completions()
|
||||
|
||||
|
||||
def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
|
||||
notices=True, speed=True):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
# TODO move to a better place.
|
||||
def usages(evaluator, definitions, search_name, mods):
|
||||
def compare_array(definitions):
|
||||
""" `definitions` are being compared by module/start_pos, because
|
||||
sometimes the id's of the objects change (e.g. executions).
|
||||
"""
|
||||
result = []
|
||||
for d in definitions:
|
||||
module = d.get_parent_until()
|
||||
result.append((module, d.start_pos))
|
||||
return result
|
||||
|
||||
def check_call(call):
|
||||
result = []
|
||||
follow = [] # There might be multiple search_name's in one call_path
|
||||
call_path = list(call.generate_call_path())
|
||||
for i, name in enumerate(call_path):
|
||||
# name is `pr.NamePart`.
|
||||
if name == search_name:
|
||||
follow.append(call_path[:i + 1])
|
||||
|
||||
for f in follow:
|
||||
follow_res, search = evaluator.goto(call.parent, f)
|
||||
# names can change (getattr stuff), therefore filter names that
|
||||
# don't match `search_name`.
|
||||
|
||||
# TODO add something like that in the future - for now usages are
|
||||
# completely broken anyway.
|
||||
#follow_res = [r for r in follow_res if str(r) == search]
|
||||
#print search.start_pos,search_name.start_pos
|
||||
#print follow_res, search, search_name, [(r, r.start_pos) for r in follow_res]
|
||||
follow_res = usages_add_import_modules(evaluator, follow_res, search)
|
||||
|
||||
compare_follow_res = compare_array(follow_res)
|
||||
# compare to see if they match
|
||||
if any(r in compare_definitions for r in compare_follow_res):
|
||||
scope = call.parent
|
||||
result.append(classes.Usage(evaluator, search, scope))
|
||||
|
||||
return result
|
||||
|
||||
if not definitions:
|
||||
return set()
|
||||
|
||||
compare_definitions = compare_array(definitions)
|
||||
mods |= set([d.get_parent_until() for d in definitions])
|
||||
names = []
|
||||
for m in imports.get_modules_containing_name(mods, search_name):
|
||||
try:
|
||||
stmts = m.used_names[search_name]
|
||||
except KeyError:
|
||||
continue
|
||||
for stmt in stmts:
|
||||
if isinstance(stmt, pr.Import):
|
||||
count = 0
|
||||
imps = []
|
||||
for i in stmt.get_all_import_names():
|
||||
for name_part in i.names:
|
||||
count += 1
|
||||
if name_part == search_name:
|
||||
imps.append((count, name_part))
|
||||
|
||||
for used_count, name_part in imps:
|
||||
i = imports.ImportPath(evaluator, stmt, kill_count=count - used_count,
|
||||
direct_resolve=True)
|
||||
f = i.follow(is_goto=True)
|
||||
if set(f) & set(definitions):
|
||||
names.append(classes.Usage(evaluator, name_part, stmt))
|
||||
else:
|
||||
for call in helpers.scan_statement_for_calls(stmt, search_name, assignment_details=True):
|
||||
names += check_call(call)
|
||||
return names
|
||||
|
||||
|
||||
def usages_add_import_modules(evaluator, definitions, search_name):
|
||||
""" Adds the modules of the imports """
|
||||
new = set()
|
||||
for d in definitions:
|
||||
if isinstance(d.parent, pr.Import):
|
||||
s = imports.ImportPath(evaluator, d.parent, direct_resolve=True)
|
||||
with common.ignored(IndexError):
|
||||
new.add(s.follow(is_goto=True)[0])
|
||||
return set(definitions) | new
|
||||
625
jedi/api/classes.py
Normal file
625
jedi/api/classes.py
Normal file
@@ -0,0 +1,625 @@
|
||||
"""
|
||||
The :mod:`api_classes` module contains the return classes of the API. These
|
||||
classes are the much bigger part of the whole API, because they contain the
|
||||
interesting information about completion and goto operations.
|
||||
"""
|
||||
import warnings
|
||||
import functools
|
||||
|
||||
from jedi._compatibility import unicode, next
|
||||
from jedi import settings
|
||||
from jedi import common
|
||||
from jedi import cache
|
||||
from jedi.parser import representation as pr
|
||||
from jedi.evaluate import representation as er
|
||||
from jedi.evaluate import iterable
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.api import keywords
|
||||
from jedi.evaluate.finder import get_names_of_scope
|
||||
|
||||
|
||||
def clear_caches():
|
||||
"""
|
||||
Clear all caches of this and related modules. The only cache that will not
|
||||
be deleted is the module cache.
|
||||
"""
|
||||
cache.clear_caches()
|
||||
|
||||
|
||||
def _clear_caches_after_call(func):
|
||||
"""
|
||||
Clear caches just before returning a value.
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwds):
|
||||
result = func(*args, **kwds)
|
||||
clear_caches()
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
|
||||
class BaseDefinition(object):
|
||||
_mapping = {
|
||||
'posixpath': 'os.path',
|
||||
'riscospath': 'os.path',
|
||||
'ntpath': 'os.path',
|
||||
'os2emxpath': 'os.path',
|
||||
'macpath': 'os.path',
|
||||
'genericpath': 'os.path',
|
||||
'posix': 'os',
|
||||
'_io': 'io',
|
||||
'_functools': 'functools',
|
||||
'_sqlite3': 'sqlite3',
|
||||
'__builtin__': '',
|
||||
'builtins': '',
|
||||
}
|
||||
|
||||
_tuple_mapping = dict((tuple(k.split('.')), v) for (k, v) in {
|
||||
'argparse._ActionsContainer': 'argparse.ArgumentParser',
|
||||
'_sre.SRE_Match': 're.MatchObject',
|
||||
'_sre.SRE_Pattern': 're.RegexObject',
|
||||
}.items())
|
||||
|
||||
def __init__(self, evaluator, definition, start_pos):
|
||||
self._evaluator = evaluator
|
||||
self._start_pos = start_pos
|
||||
self._definition = definition
|
||||
"""
|
||||
An instance of :class:`jedi.parsing_representation.Base` subclass.
|
||||
"""
|
||||
self.is_keyword = isinstance(definition, keywords.Keyword)
|
||||
|
||||
# generate a path to the definition
|
||||
self._module = definition.get_parent_until()
|
||||
if self.in_builtin_module():
|
||||
self.module_path = None
|
||||
else:
|
||||
self.module_path = self._module.path
|
||||
"""Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``"""
|
||||
|
||||
@property
|
||||
def start_pos(self):
|
||||
"""
|
||||
.. deprecated:: 0.7.0
|
||||
Use :attr:`.line` and :attr:`.column` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
warnings.warn("Use line/column instead.", DeprecationWarning)
|
||||
return self._start_pos
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""
|
||||
The type of the definition.
|
||||
|
||||
Here is an example of the value of this attribute. Let's consider
|
||||
the following source. As what is in ``variable`` is unambiguous
|
||||
to Jedi, :meth:`api.Script.goto_definitions` should return a list of
|
||||
definition for ``sys``, ``f``, ``C`` and ``x``.
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = '''
|
||||
... import keyword
|
||||
...
|
||||
... class C:
|
||||
... pass
|
||||
...
|
||||
... class D:
|
||||
... pass
|
||||
...
|
||||
... x = D()
|
||||
...
|
||||
... def f():
|
||||
... pass
|
||||
...
|
||||
... variable = keyword or f or C or x'''
|
||||
>>> script = Script(source, len(source.splitlines()), 3, 'example.py')
|
||||
>>> defs = script.goto_definitions()
|
||||
|
||||
Before showing what is in ``defs``, let's sort it by :attr:`line`
|
||||
so that it is easy to relate the result to the source code.
|
||||
|
||||
>>> defs = sorted(defs, key=lambda d: d.line)
|
||||
>>> defs # doctest: +NORMALIZE_WHITESPACE
|
||||
[<Definition module keyword>, <Definition class C>,
|
||||
<Definition class D>, <Definition def f>]
|
||||
|
||||
Finally, here is what you can get from :attr:`type`:
|
||||
|
||||
>>> defs[0].type
|
||||
'module'
|
||||
>>> defs[1].type
|
||||
'class'
|
||||
>>> defs[2].type
|
||||
'instance'
|
||||
>>> defs[3].type
|
||||
'function'
|
||||
|
||||
"""
|
||||
# generate the type
|
||||
stripped = self._definition
|
||||
if isinstance(stripped, compiled.CompiledObject):
|
||||
return stripped.type()
|
||||
if isinstance(stripped, er.InstanceElement):
|
||||
stripped = stripped.var
|
||||
if isinstance(stripped, pr.Name):
|
||||
stripped = stripped.parent
|
||||
return type(stripped).__name__.lower()
|
||||
|
||||
def _path(self):
|
||||
"""The module path."""
|
||||
path = []
|
||||
|
||||
def insert_nonnone(x):
|
||||
if x:
|
||||
path.insert(0, x)
|
||||
|
||||
if not isinstance(self._definition, keywords.Keyword):
|
||||
par = self._definition
|
||||
while par is not None:
|
||||
if isinstance(par, pr.Import):
|
||||
insert_nonnone(par.namespace)
|
||||
insert_nonnone(par.from_ns)
|
||||
if par.relative_count == 0:
|
||||
break
|
||||
with common.ignored(AttributeError):
|
||||
path.insert(0, par.name)
|
||||
par = par.parent
|
||||
return path
|
||||
|
||||
@property
|
||||
def module_name(self):
|
||||
"""
|
||||
The module name.
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = 'import json'
|
||||
>>> script = Script(source, path='example.py')
|
||||
>>> d = script.goto_definitions()[0]
|
||||
>>> print(d.module_name) # doctest: +ELLIPSIS
|
||||
json
|
||||
"""
|
||||
return str(self._module.name)
|
||||
|
||||
def in_builtin_module(self):
|
||||
"""Whether this is a builtin module."""
|
||||
return isinstance(self._module, compiled.CompiledObject)
|
||||
|
||||
@property
|
||||
def line_nr(self):
|
||||
"""
|
||||
.. deprecated:: 0.5.0
|
||||
Use :attr:`.line` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
warnings.warn("Use line instead.", DeprecationWarning)
|
||||
return self.line
|
||||
|
||||
@property
|
||||
def line(self):
|
||||
"""The line where the definition occurs (starting with 1)."""
|
||||
if self.in_builtin_module():
|
||||
return None
|
||||
return self._start_pos[0]
|
||||
|
||||
@property
|
||||
def column(self):
|
||||
"""The column where the definition occurs (starting with 0)."""
|
||||
if self.in_builtin_module():
|
||||
return None
|
||||
return self._start_pos[1]
|
||||
|
||||
@property
|
||||
def doc(self):
|
||||
r"""
|
||||
Return a document string for this completion object.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = '''\
|
||||
... def f(a, b=1):
|
||||
... "Document for function f."
|
||||
... '''
|
||||
>>> script = Script(source, 1, len('def f'), 'example.py')
|
||||
>>> d = script.goto_definitions()[0]
|
||||
>>> print(d.doc)
|
||||
f(a, b = 1)
|
||||
<BLANKLINE>
|
||||
Document for function f.
|
||||
|
||||
Notice that useful extra information is added to the actual
|
||||
docstring. For function, it is call signature. If you need
|
||||
actual docstring, use :attr:`raw_doc` instead.
|
||||
|
||||
>>> print(d.raw_doc)
|
||||
Document for function f.
|
||||
|
||||
"""
|
||||
try:
|
||||
return self._definition.doc or '' # Always a String, never None.
|
||||
except AttributeError:
|
||||
return self.raw_doc
|
||||
|
||||
@property
|
||||
def raw_doc(self):
|
||||
"""
|
||||
The raw docstring ``__doc__`` for any object.
|
||||
|
||||
See :attr:`doc` for example.
|
||||
"""
|
||||
if isinstance(self._definition.docstr, pr.token_pr.TokenDocstring):
|
||||
return unicode(self._definition.docstr.as_string())
|
||||
try:
|
||||
return unicode(self._definition.docstr)
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""A textual description of the object."""
|
||||
return unicode(self._definition)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
"""
|
||||
Dot-separated path of this object.
|
||||
|
||||
It is in the form of ``<module>[.<submodule>[...]][.<object>]``.
|
||||
It is useful when you want to look up Python manual of the
|
||||
object at hand.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = '''
|
||||
... import os
|
||||
... os.path.join'''
|
||||
>>> script = Script(source, 3, len('os.path.join'), 'example.py')
|
||||
>>> print(script.goto_definitions()[0].full_name)
|
||||
os.path.join
|
||||
|
||||
Notice that it correctly returns ``'os.path.join'`` instead of
|
||||
(for example) ``'posixpath.join'``.
|
||||
|
||||
"""
|
||||
path = [unicode(p) for p in self._path()]
|
||||
# TODO add further checks, the mapping should only occur on stdlib.
|
||||
if not path:
|
||||
return None # for keywords the path is empty
|
||||
|
||||
with common.ignored(KeyError):
|
||||
path[0] = self._mapping[path[0]]
|
||||
for key, repl in self._tuple_mapping.items():
|
||||
if tuple(path[:len(key)]) == key:
|
||||
path = [repl] + path[len(key):]
|
||||
|
||||
return '.'.join(path if path[0] else path[1:])
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s>" % (type(self).__name__, self.description)
|
||||
|
||||
|
||||
class Completion(BaseDefinition):
|
||||
"""
|
||||
`Completion` objects are returned from :meth:`api.Script.completions`. They
|
||||
provide additional information about a completion.
|
||||
"""
|
||||
def __init__(self, evaluator, name, needs_dot, like_name_length, base):
|
||||
super(Completion, self).__init__(evaluator, name.parent, name.start_pos)
|
||||
|
||||
self._name = name
|
||||
self._needs_dot = needs_dot
|
||||
self._like_name_length = like_name_length
|
||||
self._base = base
|
||||
|
||||
# Completion objects with the same Completion name (which means
|
||||
# duplicate items in the completion)
|
||||
self._same_name_completions = []
|
||||
|
||||
def _complete(self, like_name):
|
||||
dot = '.' if self._needs_dot else ''
|
||||
append = ''
|
||||
if settings.add_bracket_after_function \
|
||||
and self.type == 'Function':
|
||||
append = '('
|
||||
|
||||
if settings.add_dot_after_module:
|
||||
if isinstance(self._base, pr.Module):
|
||||
append += '.'
|
||||
if isinstance(self._base, pr.Param):
|
||||
append += '='
|
||||
|
||||
name = self._name.names[-1]
|
||||
if like_name:
|
||||
name = name[self._like_name_length:]
|
||||
return dot + name + append
|
||||
|
||||
@property
|
||||
def complete(self):
|
||||
"""
|
||||
Return the rest of the word, e.g. completing ``isinstance``::
|
||||
|
||||
isinstan# <-- Cursor is here
|
||||
|
||||
would return the string 'ce'. It also adds additional stuff, depending
|
||||
on your `settings.py`.
|
||||
"""
|
||||
return self._complete(True)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Similar to :meth:`Completion.complete`, but return the whole word, for
|
||||
example::
|
||||
|
||||
isinstan
|
||||
|
||||
would return `isinstance`.
|
||||
"""
|
||||
return unicode(self._name.names[-1])
|
||||
|
||||
@property
|
||||
def name_with_symbols(self):
|
||||
"""
|
||||
Similar to :meth:`Completion.name`, but like :meth:`Completion.name`
|
||||
returns also the symbols, for example::
|
||||
|
||||
list()
|
||||
|
||||
would return ``.append`` and others (which means it adds a dot).
|
||||
"""
|
||||
return self._complete(False)
|
||||
|
||||
@property
|
||||
def word(self):
|
||||
"""
|
||||
.. deprecated:: 0.6.0
|
||||
Use :attr:`.name` instead.
|
||||
.. todo:: Remove!
|
||||
"""
|
||||
warnings.warn("Use name instead.", DeprecationWarning)
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""Provide a description of the completion object."""
|
||||
parent = self._name.parent
|
||||
if parent is None:
|
||||
return ''
|
||||
t = self.type
|
||||
if t == 'statement' or t == 'import':
|
||||
desc = self._definition.get_code(False)
|
||||
else:
|
||||
desc = '.'.join(unicode(p) for p in self._path())
|
||||
|
||||
line = '' if self.in_builtin_module else '@%s' % self.line
|
||||
return '%s: %s%s' % (t, desc, line)
|
||||
|
||||
@cache.underscore_memoization
|
||||
def follow_definition(self):
|
||||
"""
|
||||
Return the original definitions. I strongly recommend not using it for
|
||||
your completions, because it might slow down |jedi|. If you want to
|
||||
read only a few objects (<=20), it might be useful, especially to get
|
||||
the original docstrings. The basic problem of this function is that it
|
||||
follows all results. This means with 1000 completions (e.g. numpy),
|
||||
it's just PITA-slow.
|
||||
"""
|
||||
if self._definition.isinstance(pr.Statement):
|
||||
defs = self._evaluator.eval_statement(self._definition)
|
||||
elif self._definition.isinstance(pr.Import):
|
||||
defs = imports.strip_imports(self._evaluator, [self._definition])
|
||||
else:
|
||||
return [self]
|
||||
|
||||
defs = [BaseDefinition(self._evaluator, d, d.start_pos) for d in defs]
|
||||
clear_caches()
|
||||
return defs
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, self._name)
|
||||
|
||||
|
||||
class Definition(BaseDefinition):
|
||||
"""
|
||||
*Definition* objects are returned from :meth:`api.Script.goto_assignments`
|
||||
or :meth:`api.Script.goto_definitions`.
|
||||
"""
|
||||
def __init__(self, evaluator, definition):
|
||||
super(Definition, self).__init__(evaluator, definition, definition.start_pos)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Name of variable/function/class/module.
|
||||
|
||||
For example, for ``x = None`` it returns ``'x'``.
|
||||
|
||||
:rtype: str or None
|
||||
"""
|
||||
d = self._definition
|
||||
if isinstance(d, er.InstanceElement):
|
||||
d = d.var
|
||||
|
||||
if isinstance(d, compiled.CompiledObject):
|
||||
return d.name
|
||||
elif isinstance(d, pr.Name):
|
||||
return d.names[-1] if d.names else None
|
||||
elif isinstance(d, iterable.Array):
|
||||
return unicode(d.type)
|
||||
elif isinstance(d, (pr.Class, er.Class, er.Instance,
|
||||
er.Function, pr.Function)):
|
||||
return unicode(d.name)
|
||||
elif isinstance(d, pr.Module):
|
||||
return self.module_name
|
||||
elif isinstance(d, pr.Import):
|
||||
try:
|
||||
return d.get_defined_names()[0].names[-1]
|
||||
except (AttributeError, IndexError):
|
||||
return None
|
||||
elif isinstance(d, pr.Statement):
|
||||
try:
|
||||
return d.assignment_details[0][1].values[0][0].name.names[-1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""
|
||||
A description of the :class:`.Definition` object, which is heavily used
|
||||
in testing. e.g. for ``isinstance`` it returns ``def isinstance``.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = '''
|
||||
... def f():
|
||||
... pass
|
||||
...
|
||||
... class C:
|
||||
... pass
|
||||
...
|
||||
... variable = f or C'''
|
||||
>>> script = Script(source, column=3) # line is maximum by default
|
||||
>>> defs = script.goto_definitions()
|
||||
>>> defs = sorted(defs, key=lambda d: d.line)
|
||||
>>> defs
|
||||
[<Definition def f>, <Definition class C>]
|
||||
>>> str(defs[0].description) # strip literals in python2
|
||||
'def f'
|
||||
>>> str(defs[1].description)
|
||||
'class C'
|
||||
|
||||
"""
|
||||
d = self._definition
|
||||
if isinstance(d, er.InstanceElement):
|
||||
d = d.var
|
||||
if isinstance(d, pr.Name):
|
||||
d = d.parent
|
||||
|
||||
if isinstance(d, compiled.CompiledObject):
|
||||
d = d.type() + ' ' + d.name
|
||||
elif isinstance(d, iterable.Array):
|
||||
d = 'class ' + d.type
|
||||
elif isinstance(d, (pr.Class, er.Class, er.Instance)):
|
||||
d = 'class ' + unicode(d.name)
|
||||
elif isinstance(d, (er.Function, pr.Function)):
|
||||
d = 'def ' + unicode(d.name)
|
||||
elif isinstance(d, pr.Module):
|
||||
# only show module name
|
||||
d = 'module %s' % self.module_name
|
||||
elif self.is_keyword:
|
||||
d = 'keyword %s' % d.name
|
||||
else:
|
||||
code = d.get_code().replace('\n', '')
|
||||
max_len = 20
|
||||
d = (code[:max_len] + '...') if len(code) > max_len + 3 else code
|
||||
return d
|
||||
|
||||
@property
|
||||
def desc_with_module(self):
|
||||
"""
|
||||
In addition to the definition, also return the module.
|
||||
|
||||
.. warning:: Don't use this function yet, its behaviour may change. If
|
||||
you really need it, talk to me.
|
||||
|
||||
.. todo:: Add full path. This function is should return a
|
||||
`module.class.function` path.
|
||||
"""
|
||||
position = '' if self.in_builtin_module else '@%s' % (self.line)
|
||||
return "%s:%s%s" % (self.module_name, self.description, position)
|
||||
|
||||
def defined_names(self):
|
||||
"""
|
||||
List sub-definitions (e.g., methods in class).
|
||||
|
||||
:rtype: list of Definition
|
||||
"""
|
||||
d = self._definition
|
||||
if isinstance(d, er.InstanceElement):
|
||||
d = d.var
|
||||
if isinstance(d, pr.Name):
|
||||
d = d.parent
|
||||
return defined_names(self._evaluator, d)
|
||||
|
||||
|
||||
def defined_names(evaluator, scope):
|
||||
"""
|
||||
List sub-definitions (e.g., methods in class).
|
||||
|
||||
:type scope: Scope
|
||||
:rtype: list of Definition
|
||||
"""
|
||||
pair = next(get_names_of_scope(evaluator, scope, star_search=False,
|
||||
include_builtin=False), None)
|
||||
names = pair[1] if pair else []
|
||||
return [Definition(evaluator, d) for d in sorted(names, key=lambda s: s.start_pos)]
|
||||
|
||||
|
||||
class Usage(BaseDefinition):
|
||||
"""TODO: document this"""
|
||||
def __init__(self, evaluator, name_part, scope):
|
||||
super(Usage, self).__init__(evaluator, scope, name_part.start_pos)
|
||||
self.text = unicode(name_part)
|
||||
self.end_pos = name_part.end_pos
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return "%s@%s,%s" % (self.text, self.line, self.column)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._start_pos == other._start_pos \
|
||||
and self.module_path == other.module_path
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._start_pos, self.module_path))
|
||||
|
||||
|
||||
class CallDef(object):
|
||||
"""
|
||||
`CallDef` objects is the return value of `Script.function_definition`.
|
||||
It knows what functions you are currently in. e.g. `isinstance(` would
|
||||
return the `isinstance` function. without `(` it would return nothing.
|
||||
"""
|
||||
def __init__(self, executable, index, call):
|
||||
self._executable = executable
|
||||
self.index = index
|
||||
self._call = call
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
if self._executable.isinstance(er.Function):
|
||||
if isinstance(self._executable, er.InstanceElement):
|
||||
return self._executable.params[1:]
|
||||
return self._executable.params
|
||||
else:
|
||||
try:
|
||||
sub = self._executable.get_subscope_by_name('__init__')
|
||||
return sub.params[1:] # ignore self
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
@property
|
||||
def bracket_start(self):
|
||||
""" The indent of the bracket that is responsible for the last function
|
||||
call. """
|
||||
c = self._call
|
||||
while c.next is not None:
|
||||
c = c.next
|
||||
return c.name.end_pos
|
||||
|
||||
@property
|
||||
def call_name(self):
|
||||
""" The name (e.g. 'isinstance') as a string. """
|
||||
return unicode(self._executable.name)
|
||||
|
||||
@property
|
||||
def module(self):
|
||||
return self._executable.get_parent_until()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s index %s>' % (type(self).__name__, self._executable,
|
||||
self.index)
|
||||
92
jedi/api/interpreter.py
Normal file
92
jedi/api/interpreter.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import inspect
|
||||
import re
|
||||
|
||||
from jedi._compatibility import builtins
|
||||
from jedi import debug
|
||||
from jedi.cache import underscore_memoization
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.compiled.fake import get_module
|
||||
from jedi.parser import representation as pr
|
||||
from jedi.parser.fast import FastParser
|
||||
from jedi.evaluate import helpers
|
||||
|
||||
|
||||
class InterpreterNamespace(pr.Module):
|
||||
def __init__(self, evaluator, namespace, parser_module):
|
||||
self.namespace = namespace
|
||||
self.parser_module = parser_module
|
||||
self._evaluator = evaluator
|
||||
|
||||
def get_defined_names(self):
|
||||
for name in self.parser_module.get_defined_names():
|
||||
yield name
|
||||
for key, value in self.namespace.items():
|
||||
yield LazyName(self._evaluator, key, value)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.parser_module, name)
|
||||
|
||||
|
||||
class LazyName(helpers.FakeName):
|
||||
def __init__(self, evaluator, name, value):
|
||||
super(LazyName, self).__init__(name)
|
||||
self._evaluator = evaluator
|
||||
self._value = value
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
@underscore_memoization
|
||||
def parent(self):
|
||||
parser_path = []
|
||||
obj = self._value
|
||||
if inspect.ismodule(obj):
|
||||
module = obj
|
||||
else:
|
||||
try:
|
||||
o = obj.__objclass__
|
||||
parser_path.append(pr.NamePart(obj.__name__, None, None))
|
||||
obj = o
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
module_name = obj.__module__
|
||||
parser_path.insert(0, pr.NamePart(obj.__name__, None, None))
|
||||
except AttributeError:
|
||||
# Unfortunately in some cases like `int` there's no __module__
|
||||
module = builtins
|
||||
else:
|
||||
module = __import__(module_name)
|
||||
raw_module = get_module(self._value)
|
||||
|
||||
try:
|
||||
path = module.__file__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
path = re.sub('c$', '', path)
|
||||
if path.endswith('.py'):
|
||||
# cut the `c` from `.pyc`
|
||||
with open(path) as f:
|
||||
mod = FastParser(f.read(), path[:-1]).module
|
||||
if not parser_path:
|
||||
return mod
|
||||
found = self._evaluator.eval_call_path(iter(parser_path), mod, None)
|
||||
if found:
|
||||
return found[0]
|
||||
debug.warning('Interpreter lookup for Python code failed %s',
|
||||
mod)
|
||||
|
||||
module = compiled.CompiledObject(raw_module)
|
||||
return compiled.create(self._value, module, module)
|
||||
|
||||
@parent.setter
|
||||
def parent(self, value):
|
||||
"""Needed because of the ``representation.Simple`` super class."""
|
||||
|
||||
|
||||
def create(evaluator, namespace, parser_module):
|
||||
ns = InterpreterNamespace(evaluator, namespace, parser_module)
|
||||
for attr_name in pr.SCOPE_CONTENTS:
|
||||
for something in getattr(parser_module, attr_name):
|
||||
something.parent = ns
|
||||
100
jedi/api/keywords.py
Normal file
100
jedi/api/keywords.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import pydoc
|
||||
import keyword
|
||||
|
||||
from jedi._compatibility import is_py3
|
||||
from jedi import common
|
||||
from jedi.evaluate import compiled
|
||||
|
||||
try:
|
||||
from pydoc_data import topics as pydoc_topics
|
||||
except ImportError:
|
||||
# Python 2.6
|
||||
import pydoc_topics
|
||||
|
||||
if is_py3:
|
||||
keys = keyword.kwlist
|
||||
else:
|
||||
keys = keyword.kwlist + ['None', 'False', 'True']
|
||||
|
||||
|
||||
def keywords(string='', pos=(0, 0), all=False):
|
||||
if all:
|
||||
return set([Keyword(k, pos) for k in keys])
|
||||
if string in keys:
|
||||
return set([Keyword(string, pos)])
|
||||
return set()
|
||||
|
||||
|
||||
def keyword_names(*args, **kwargs):
|
||||
kwds = []
|
||||
for k in keywords(*args, **kwargs):
|
||||
start = k.start_pos
|
||||
kwds.append(KeywordName(k, k.name, start))
|
||||
return kwds
|
||||
|
||||
|
||||
def get_operator(string, pos):
|
||||
return Keyword(string, pos)
|
||||
|
||||
|
||||
class KeywordName(object):
|
||||
def __init__(self, parent, name, start_pos):
|
||||
self.parent = parent
|
||||
self.names = [name]
|
||||
self.start_pos = start_pos
|
||||
|
||||
@property
|
||||
def end_pos(self):
|
||||
return self.start_pos[0], self.start_pos[1] + len(self.name)
|
||||
|
||||
|
||||
class Keyword(object):
|
||||
def __init__(self, name, pos):
|
||||
self.name = name
|
||||
self.start_pos = pos
|
||||
self.parent = compiled.builtin
|
||||
|
||||
def get_parent_until(self):
|
||||
return self.parent
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
""" For a `parsing.Name` like comparision """
|
||||
return [self.name]
|
||||
|
||||
@property
|
||||
def docstr(self):
|
||||
return imitate_pydoc(self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, self.name)
|
||||
|
||||
|
||||
def imitate_pydoc(string):
|
||||
"""
|
||||
It's not possible to get the pydoc's without starting the annoying pager
|
||||
stuff.
|
||||
"""
|
||||
# str needed because of possible unicode stuff in py2k (pydoc doesn't work
|
||||
# with unicode strings)
|
||||
string = str(string)
|
||||
h = pydoc.help
|
||||
with common.ignored(KeyError):
|
||||
# try to access symbols
|
||||
string = h.symbols[string]
|
||||
string, _, related = string.partition(' ')
|
||||
|
||||
get_target = lambda s: h.topics.get(s, h.keywords.get(s))
|
||||
while isinstance(string, str):
|
||||
string = get_target(string)
|
||||
|
||||
try:
|
||||
# is a tuple now
|
||||
label, related = string
|
||||
except TypeError:
|
||||
return ''
|
||||
|
||||
try:
|
||||
return pydoc_topics.topics[label] if pydoc_topics else ''
|
||||
except KeyError:
|
||||
return ''
|
||||
23
jedi/api/replstartup.py
Normal file
23
jedi/api/replstartup.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
To use Jedi completion in Python interpreter, add the following in your shell
|
||||
setup (e.g., ``.bashrc``)::
|
||||
|
||||
export PYTHONSTARTUP="$(python -m jedi repl)"
|
||||
|
||||
Then you will be able to use Jedi completer in your Python interpreter::
|
||||
|
||||
$ python
|
||||
Python 2.7.2+ (default, Jul 20 2012, 22:15:08)
|
||||
[GCC 4.6.1] on linux2
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import os
|
||||
>>> os.path.join().split().in<TAB> # doctest: +SKIP
|
||||
os.path.join().split().index os.path.join().split().insert
|
||||
|
||||
"""
|
||||
|
||||
import jedi.utils
|
||||
jedi.utils.setup_readline()
|
||||
del jedi
|
||||
# Note: try not to do many things here, as it will contaminate global
|
||||
# namespace of the interpreter.
|
||||
Reference in New Issue
Block a user