mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 14:34:31 +08:00
It is better to check that Interpreter does not fail with the real name space. Previously the doctest using locals() failed because of the bug in _import_raw_namespace.
753 lines
27 KiB
Python
753 lines
27 KiB
Python
"""
|
|
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.
|
|
"""
|
|
from __future__ import with_statement
|
|
|
|
import re
|
|
import os
|
|
import warnings
|
|
import itertools
|
|
import tokenize
|
|
|
|
from jedi import parsing
|
|
from jedi import parsing_representation as pr
|
|
from jedi import debug
|
|
from jedi import settings
|
|
from jedi import helpers
|
|
from jedi import common
|
|
from jedi import cache
|
|
from jedi import modules
|
|
from jedi._compatibility import next, unicode
|
|
import evaluate
|
|
import keywords
|
|
import api_classes
|
|
import evaluate_representation as er
|
|
import dynamic
|
|
import imports
|
|
import builtin
|
|
|
|
|
|
class NotFoundError(Exception):
|
|
"""A custom error to avoid catching the wrong exceptions."""
|
|
pass
|
|
|
|
|
|
class Script(object):
|
|
"""
|
|
A Script is the base for completions, goto or whatever you want to do with
|
|
|jedi|.
|
|
|
|
: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 source_path: The path of the file in the file system, or ``''`` if
|
|
it hasn't been saved yet.
|
|
:type source_path: str or None
|
|
:param source_encoding: The encoding of ``source``, if it is not a
|
|
``unicode`` object (default ``'utf-8'``).
|
|
:type source_encoding: str
|
|
"""
|
|
def __init__(self, source, line, column, source_path,
|
|
source_encoding='utf-8', fast=True):
|
|
api_classes._clear_caches()
|
|
debug.reset_time()
|
|
self.source = modules.source_to_unicode(source, source_encoding)
|
|
self.pos = line, column
|
|
self._module = modules.ModuleWithCursor(
|
|
source_path, source=self.source, position=self.pos, fast=fast)
|
|
self._source_path = source_path
|
|
self.source_path = None if source_path is None \
|
|
else os.path.abspath(source_path)
|
|
debug.speed('init')
|
|
|
|
def __repr__(self):
|
|
return '<%s: %s>' % (self.__class__.__name__, repr(self._source_path))
|
|
|
|
@property
|
|
def _parser(self):
|
|
""" lazy parser."""
|
|
return self._module.parser
|
|
|
|
@api_classes._clear_caches_after_call
|
|
def completions(self):
|
|
"""
|
|
Return :class:`api_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:`api_classes.Completion`
|
|
"""
|
|
debug.speed('completions start')
|
|
path = self._module.get_path_until_cursor()
|
|
if re.search('^\.|\.\.$', path):
|
|
return []
|
|
path, dot, like = self._get_completion_parts(path)
|
|
completion_line = self._module.get_line(self.pos[0])[:self.pos[1]]
|
|
|
|
try:
|
|
scopes = list(self._prepare_goto(path, True))
|
|
except NotFoundError:
|
|
scopes = []
|
|
scope_generator = evaluate.get_names_of_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(er.Function):
|
|
names = s.get_magic_method_names()
|
|
else:
|
|
if isinstance(s, imports.ImportPath):
|
|
if like == 'import':
|
|
if not completion_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))
|
|
|
|
if not dot: # named params have no dots
|
|
for call_def in self.call_signatures():
|
|
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.
|
|
u = self._parser.user_stmt
|
|
bs = builtin.Builtin.scope
|
|
if isinstance(u, pr.Import):
|
|
if (u.relative_count > 0 or u.from_ns) and not re.search(
|
|
r'(,|from)\s*$|import\s+$', completion_line):
|
|
completions += ((k, bs) for k
|
|
in keywords.get_keywords('import'))
|
|
|
|
if not path and not isinstance(u, pr.Import):
|
|
# add keywords
|
|
completions += ((k, bs) for k in keywords.get_keywords(
|
|
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 evaluate.filter_private_variable(s,
|
|
self._parser.user_stmt, n):
|
|
new = api_classes.Completion(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 _prepare_goto(self, goto_path, is_like_search=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
|
|
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, pr.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):
|
|
offset = self.pos[0] - 1, self.pos[1]
|
|
r = parsing.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
|
|
|
|
@api_classes._clear_caches_after_call
|
|
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:`api_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._module.get_path_under_cursor()
|
|
|
|
context = self._module.get_context()
|
|
scopes = set()
|
|
lower_priority_operators = ('()', '(', ',')
|
|
"""Operators that could hide callee."""
|
|
if next(context) in ('class', 'def'):
|
|
scopes = set([self._module.parser.user_scope])
|
|
elif not goto_path:
|
|
op = self._module.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 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
|
|
self.pos = (row, max(col - 1, 0))
|
|
self._module = modules.ModuleWithCursor(
|
|
self._source_path,
|
|
source=self.source,
|
|
position=self.pos)
|
|
# then try to find the path again
|
|
goto_path = self._module.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.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 self._sorted_defs(d)
|
|
|
|
@api_classes._clear_caches_after_call
|
|
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:`api_classes.Definition`
|
|
"""
|
|
d = [api_classes.Definition(d) for d in set(self._goto()[0])]
|
|
return self._sorted_defs(d)
|
|
|
|
def _goto(self, add_import_name=False):
|
|
"""
|
|
Used for goto_assignments and usages.
|
|
|
|
: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, pr.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()
|
|
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()
|
|
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 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)
|
|
if isinstance(user_stmt, pr.Statement):
|
|
if user_stmt.get_commands()[0].start_pos > self.pos:
|
|
# The cursor must be after the start, otherwise the
|
|
# statement is just an assignee.
|
|
definitions = [user_stmt]
|
|
return definitions, search_name
|
|
|
|
@api_classes._clear_caches_after_call
|
|
def usages(self, additional_module_paths=()):
|
|
"""
|
|
Return :class:`api_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:`api_classes.Usage`
|
|
"""
|
|
user_stmt = self._parser.user_stmt
|
|
definitions, search_name = self._goto(add_import_name=True)
|
|
if isinstance(user_stmt, pr.Statement) \
|
|
and self.pos < user_stmt.get_commands()[0].start_pos:
|
|
# the search_name might be before `=`
|
|
definitions = [v for v in user_stmt.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 = dynamic.usages_add_import_modules(definitions,
|
|
search_name)
|
|
|
|
module = set([d.get_parent_until() for d in definitions])
|
|
module.add(self._parser.module)
|
|
names = dynamic.usages(definitions, search_name, module)
|
|
|
|
for d in set(definitions):
|
|
if isinstance(d, pr.Module):
|
|
names.append(api_classes.Usage(d, d))
|
|
else:
|
|
names.append(api_classes.Usage(d.names[-1], d))
|
|
|
|
return self._sorted_defs(set(names))
|
|
|
|
@api_classes._clear_caches_after_call
|
|
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: :class:`api_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_function_definition):
|
|
_callable = lambda: evaluate.follow_call(call)
|
|
origins = cache.cache_function_definition(_callable, user_stmt)
|
|
debug.speed('func_call followed')
|
|
|
|
return [api_classes.CallDef(o, index, call) for o in origins]
|
|
|
|
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_function_definition(
|
|
user_stmt, self.pos)
|
|
debug.speed('func_call parsed')
|
|
return call, index
|
|
|
|
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()
|
|
|
|
@staticmethod
|
|
def _sorted_defs(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.start_pos))
|
|
|
|
|
|
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.complete()[0].word)
|
|
upper
|
|
|
|
"""
|
|
|
|
def __init__(self, source, namespaces=[], line=None, column=None,
|
|
source_path=None, source_encoding='utf-8'):
|
|
"""
|
|
Parse `source` and mixin interpreted Python objects from `namespaces`.
|
|
|
|
:type source: str
|
|
:arg source:
|
|
:type namespaces: list of dict
|
|
:arg namespaces:
|
|
|
|
Other optional arguments are same as the ones for :class:`Script`.
|
|
"""
|
|
lines = source.splitlines()
|
|
line = len(lines) if line is None else line
|
|
column = len(lines[-1]) if column is None else column
|
|
super(Interpreter, self).__init__(
|
|
source, line, column, source_path, source_encoding, fast=False)
|
|
|
|
count = itertools.count()
|
|
self._genname = lambda: '*jedi-%s*' % next(count)
|
|
"""
|
|
Generate unique variable names to avoid name collision.
|
|
To avoid name collision to already defined names, generated
|
|
names are invalid as Python identifier.
|
|
"""
|
|
|
|
for ns in namespaces:
|
|
self._import_raw_namespace(ns)
|
|
|
|
def _import_raw_namespace(self, raw_namespace):
|
|
"""
|
|
Import interpreted Python objects in a namespace.
|
|
|
|
Three kinds of objects are treated here.
|
|
|
|
1. Functions and classes. The objects imported like this::
|
|
|
|
from os.path import join
|
|
|
|
2. Modules. The objects imported like this::
|
|
|
|
import os
|
|
|
|
3. Instances. The objects created like this::
|
|
|
|
from datetime import datetime
|
|
dt = datetime(2013, 1, 1)
|
|
|
|
:type raw_namespace: dict
|
|
:arg raw_namespace: e.g., the dict given by `locals`
|
|
"""
|
|
scope = self._parser.scope
|
|
for (variable, obj) in raw_namespace.items():
|
|
objname = getattr(obj, '__name__', None)
|
|
|
|
# Import functions and classes
|
|
module = getattr(obj, '__module__', None)
|
|
if module and objname:
|
|
fakeimport = self._make_fakeimport(module, objname, variable)
|
|
scope.add_import(fakeimport)
|
|
continue
|
|
|
|
# Import modules
|
|
if getattr(obj, '__file__', None) and objname:
|
|
fakeimport = self._make_fakeimport(objname)
|
|
scope.add_import(fakeimport)
|
|
continue
|
|
|
|
# Import instances
|
|
objclass = getattr(obj, '__class__', None)
|
|
module = getattr(objclass, '__module__', None)
|
|
if objclass and module:
|
|
alias = self._genname()
|
|
fakeimport = self._make_fakeimport(module, objclass.__name__,
|
|
alias)
|
|
fakestmt = self._make_fakestatement(variable, alias, call=True)
|
|
scope.add_import(fakeimport)
|
|
scope.add_statement(fakestmt)
|
|
continue
|
|
|
|
def _make_fakeimport(self, module, variable=None, alias=None):
|
|
"""
|
|
Make a fake import object.
|
|
|
|
The following statements are created depending on what parameters
|
|
are given:
|
|
|
|
- only `module` is given: ``import <module>``
|
|
- `module` and `variable` are given: ``from <module> import <variable>``
|
|
- all params are given: ``from <module> import <variable> as <alias>``
|
|
|
|
:type module: str
|
|
:arg module: ``<module>`` part in ``from <module> import ...``
|
|
:type variable: str
|
|
:arg variable: ``<variable>`` part in ``from ... import <variable>``
|
|
:type alias: str
|
|
:arg alias: ``<alias>`` part in ``... import ... as <alias>``.
|
|
|
|
:rtype: :class:`parsing_representation.Import`
|
|
"""
|
|
submodule = self._parser.scope._sub_module
|
|
if variable:
|
|
varname = pr.Name(
|
|
module=submodule,
|
|
names=[(variable, (-1, 0))],
|
|
start_pos=(-1, 0),
|
|
end_pos=(None, None))
|
|
else:
|
|
varname = None
|
|
modname = pr.Name(
|
|
module=submodule,
|
|
names=[(module, (-1, 0))],
|
|
start_pos=(-1, 0),
|
|
end_pos=(None, None))
|
|
if alias:
|
|
aliasname = pr.Name(
|
|
module=submodule,
|
|
names=[(alias, (-1, 0))],
|
|
start_pos=(-1, 0),
|
|
end_pos=(None, None))
|
|
else:
|
|
aliasname = None
|
|
if varname:
|
|
fakeimport = pr.Import(
|
|
module=submodule,
|
|
namespace=varname,
|
|
from_ns=modname,
|
|
alias=aliasname,
|
|
start_pos=(-1, 0),
|
|
end_pos=(None, None))
|
|
else:
|
|
fakeimport = pr.Import(
|
|
module=submodule,
|
|
namespace=modname,
|
|
alias=aliasname,
|
|
start_pos=(-1, 0),
|
|
end_pos=(None, None))
|
|
return fakeimport
|
|
|
|
def _make_fakestatement(self, lhs, rhs, call=False):
|
|
"""
|
|
Make a fake statement object that represents ``lhs = rhs``.
|
|
|
|
:type call: bool
|
|
:arg call: When `call` is true, make a fake statement that represents
|
|
``lhs = rhs()``.
|
|
|
|
:rtype: :class:`parsing_representation.Statement`
|
|
"""
|
|
submodule = self._parser.scope._sub_module
|
|
lhsname = pr.Name(
|
|
module=submodule,
|
|
names=[(lhs, (0, 0))],
|
|
start_pos=(0, 0),
|
|
end_pos=(None, None))
|
|
rhsname = pr.Name(
|
|
module=submodule,
|
|
names=[(rhs, (0, 0))],
|
|
start_pos=(0, 0),
|
|
end_pos=(None, None))
|
|
token_list = [lhsname, (tokenize.OP, '=', (0, 0)), rhsname]
|
|
if call:
|
|
token_list.extend([
|
|
(tokenize.OP, '(', (0, 0)),
|
|
(tokenize.OP, ')', (0, 0)),
|
|
])
|
|
return pr.Statement(
|
|
module=submodule,
|
|
set_vars=[lhsname],
|
|
used_vars=[rhsname],
|
|
token_list=token_list,
|
|
start_pos=(0, 0),
|
|
end_pos=(None, None))
|
|
|
|
|
|
def defined_names(source, source_path=None, source_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 api_classes.Definition
|
|
"""
|
|
parser = parsing.Parser(
|
|
modules.source_to_unicode(source, source_encoding),
|
|
module_path=source_path,
|
|
)
|
|
return api_classes._defined_names(parser.scope)
|
|
|
|
|
|
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
|
|
|
|
|
|
def _quick_complete(source):
|
|
"""
|
|
Convenience function to complete a source string at the end.
|
|
|
|
Example:
|
|
|
|
>>> _quick_complete('''
|
|
... import datetime
|
|
... datetime.da''') #doctest: +ELLIPSIS
|
|
[<Completion: date>, <Completion: datetime>, ...]
|
|
|
|
:param source: The source code to be completed.
|
|
:type source: string
|
|
:return: Completion objects as returned by :meth:`complete`.
|
|
:rtype: list of :class:`api_classes.Completion`
|
|
"""
|
|
lines = re.sub(r'[\n\r\s]*$', '', source).splitlines()
|
|
pos = len(lines), len(lines[-1])
|
|
script = Script(source, pos[0], pos[1], '')
|
|
return script.completions()
|