1
0
forked from VimPlug/jedi

Merge branch 'master' into dict

This commit is contained in:
Dave Halter
2019-12-28 12:17:04 +01:00
153 changed files with 4231 additions and 1858 deletions

View File

@@ -25,12 +25,14 @@ from jedi.file_io import KnownContentFileIO
from jedi.api import classes
from jedi.api import interpreter
from jedi.api import helpers
from jedi.api.helpers import validate_line_column
from jedi.api.completion import Completion
from jedi.api.keywords import KeywordName
from jedi.api.environment import InterpreterEnvironment
from jedi.api.project import get_default_project, Project
from jedi.inference import InferenceState
from jedi.inference import imports
from jedi.inference import usages
from jedi.inference.references import find_references
from jedi.inference.arguments import try_iter_content
from jedi.inference.helpers import get_module_names, infer_call_of_leaf
from jedi.inference.sys_path import transform_path_to_dotted
@@ -68,9 +70,9 @@ class Script(object):
: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).
:param line: Deprecated, please use it directly on e.g. `.complete`
:type line: int
:param column: The column of the cursor (starting with 0).
:param column: Deprecated, please use it directly on e.g. `.complete`
:type column: int
:param path: The path of the file in the file system, or ``''`` if
it hasn't been saved yet.
@@ -126,22 +128,6 @@ class Script(object):
debug.speed('parsed')
self._code_lines = parso.split_lines(source, keepends=True)
self._code = source
line = max(len(self._code_lines), 1) if line is None else line
if not (0 < line <= len(self._code_lines)):
raise ValueError('`line` parameter is not in a valid range.')
line_string = self._code_lines[line - 1]
line_len = len(line_string)
if line_string.endswith('\r\n'):
line_len -= 1
if line_string.endswith('\n'):
line_len -= 1
column = line_len if column is None else column
if not (0 <= column <= line_len):
raise ValueError('`column` parameter (%d) is not in a valid range '
'(0-%d) for line %d (%r).' % (
column, line_len, line, line_string))
self._pos = line, column
cache.clear_time_caches()
@@ -201,27 +187,38 @@ class Script(object):
self._inference_state.environment,
)
def completions(self):
@validate_line_column
def complete(self, line=None, column=None, **kwargs):
"""
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.
:param fuzzy: Default False. Will return fuzzy completions, which means
that e.g. ``ooa`` will match ``foobar``.
:return: Completion objects, sorted by name and ``__`` comes last.
:rtype: list of :class:`classes.Completion`
"""
with debug.increase_indent_cm('completions'):
return self._complete(line, column, **kwargs)
def _complete(self, line, column, fuzzy=False): # Python 2...
with debug.increase_indent_cm('complete'):
completion = Completion(
self._inference_state, self._get_module_context(), self._code_lines,
self._pos, self.call_signatures
(line, column), self.find_signatures
)
return completion.completions()
return completion.complete(fuzzy)
def goto_definitions(self, **kwargs):
def completions(self, fuzzy=False):
# Deprecated, will be removed.
return self.complete(*self._pos, fuzzy=fuzzy)
@validate_line_column
def infer(self, line=None, column=None, **kwargs):
"""
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
definition. The big difference between :meth:`goto` and
:meth:`infer` is that :meth:`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.
@@ -231,19 +228,24 @@ class Script(object):
inference call.
:rtype: list of :class:`classes.Definition`
"""
with debug.increase_indent_cm('goto_definitions'):
return self._goto_definitions(**kwargs)
with debug.increase_indent_cm('infer'):
return self._infer(line, column, **kwargs)
def _goto_definitions(self, only_stubs=False, prefer_stubs=False):
leaf = self._module_node.get_name_of_position(self._pos)
def goto_definitions(self, **kwargs):
# Deprecated, will be removed.
return self.infer(*self._pos, **kwargs)
def _infer(self, line, column, only_stubs=False, prefer_stubs=False):
pos = line, column
leaf = self._module_node.get_name_of_position(pos)
if leaf is None:
leaf = self._module_node.get_leaf_for_position(self._pos)
if leaf is None:
leaf = self._module_node.get_leaf_for_position(pos)
if leaf is None or leaf.type == 'string':
return []
context = self._get_module_context().create_context(leaf)
values = helpers.infer_goto_definition(self._inference_state, context, leaf)
values = helpers.infer(self._inference_state, context, leaf)
values = convert_values(
values,
only_stubs=only_stubs,
@@ -257,15 +259,20 @@ class Script(object):
return helpers.sorted_definitions(set(defs))
def goto_assignments(self, follow_imports=False, follow_builtin_imports=False, **kwargs):
# Deprecated, will be removed.
return self.goto(*self._pos,
follow_imports=follow_imports,
follow_builtin_imports=follow_builtin_imports,
**kwargs)
@validate_line_column
def goto(self, line=None, column=None, **kwargs):
"""
Return the first definition found, while optionally following imports.
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.
.. note:: It is deprecated to use follow_imports and follow_builtin_imports as
positional arguments. Will be a keyword argument in 0.16.0.
:param follow_imports: The goto call will follow imports.
:param follow_builtin_imports: If follow_imports is True will decide if
it follow builtin imports.
@@ -273,11 +280,11 @@ class Script(object):
:param prefer_stubs: Prefer stubs to Python objects for this goto call.
:rtype: list of :class:`classes.Definition`
"""
with debug.increase_indent_cm('goto_assignments'):
return self._goto_assignments(follow_imports, follow_builtin_imports, **kwargs)
with debug.increase_indent_cm('goto'):
return self._goto(line, column, **kwargs)
def _goto_assignments(self, follow_imports, follow_builtin_imports,
only_stubs=False, prefer_stubs=False):
def _goto(self, line, column, follow_imports=False, follow_builtin_imports=False,
only_stubs=False, prefer_stubs=False):
def filter_follow_imports(names):
for name in names:
if name.is_import():
@@ -296,13 +303,28 @@ class Script(object):
else:
yield name
tree_name = self._module_node.get_name_of_position(self._pos)
tree_name = self._module_node.get_name_of_position((line, column))
if tree_name is None:
# Without a name we really just want to jump to the result e.g.
# executed by `foo()`, if we the cursor is after `)`.
return self.goto_definitions(only_stubs=only_stubs, prefer_stubs=prefer_stubs)
return self.infer(line, column, only_stubs=only_stubs, prefer_stubs=prefer_stubs)
name = self._get_module_context().create_name(tree_name)
names = list(name.goto())
# Make it possible to goto the super class function/attribute
# definitions, when they are overwritten.
names = []
if name.tree_name.is_definition() and name.parent_context.is_class():
class_node = name.parent_context.tree_node
class_value = self._get_module_context().create_value(class_node)
mro = class_value.py__mro__()
next(mro) # Ignore the first entry, because it's the class itself.
for cls in mro:
names = cls.goto(tree_name.value)
if names:
break
if not names:
names = list(name.goto())
if follow_imports:
names = filter_follow_imports(names)
@@ -315,42 +337,66 @@ class Script(object):
defs = [classes.Definition(self._inference_state, d) for d in set(names)]
return helpers.sorted_definitions(defs)
def usages(self, additional_module_paths=(), **kwargs):
@validate_line_column
def help(self, line=None, column=None):
"""
Works like goto and returns a list of Definition objects. Returns
additional definitions for keywords and operators.
The additional definitions are of ``Definition(...).type == 'keyword'``.
These definitions do not have a lot of value apart from their docstring
attribute, which contains the output of Python's ``help()`` function.
:rtype: list of :class:`classes.Definition`
"""
definitions = self.goto(line, column)
if definitions:
return definitions
leaf = self._module_node.get_leaf_for_position((line, column))
if leaf.type in ('keyword', 'operator', 'error_leaf'):
reserved = self._grammar._pgen_grammar.reserved_syntax_strings.keys()
if leaf.value in reserved:
name = KeywordName(self._inference_state, leaf.value)
return [classes.Definition(self._inference_state, name)]
return []
def usages(self, **kwargs):
# Deprecated, will be removed.
return self.find_references(*self._pos, **kwargs)
@validate_line_column
def find_references(self, line=None, column=None, **kwargs):
"""
Return :class:`classes.Definition` 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.
is very useful for refactoring (renaming), or to show all references of
a variable.
.. todo:: Implement additional_module_paths
:param additional_module_paths: Deprecated, never ever worked.
:param include_builtins: Default True, checks if a usage is a builtin
(e.g. ``sys``) and in that case does not return it.
:param include_builtins: Default True, checks if a reference is a
builtin (e.g. ``sys``) and in that case does not return it.
:rtype: list of :class:`classes.Definition`
"""
if additional_module_paths:
warnings.warn(
"Deprecated since version 0.12.0. This never even worked, just ignore it.",
DeprecationWarning,
stacklevel=2
)
def _usages(include_builtins=True):
tree_name = self._module_node.get_name_of_position(self._pos)
def _references(include_builtins=True):
tree_name = self._module_node.get_name_of_position((line, column))
if tree_name is None:
# Must be syntax
return []
names = usages.usages(self._get_module_context(), tree_name)
names = find_references(self._get_module_context(), tree_name)
definitions = [classes.Definition(self._inference_state, n) for n in names]
if not include_builtins:
definitions = [d for d in definitions if not d.in_builtin_module()]
return helpers.sorted_definitions(definitions)
return _usages(**kwargs)
return _references(**kwargs)
def call_signatures(self):
# Deprecated, will be removed.
return self.find_signatures(*self._pos)
@validate_line_column
def find_signatures(self, line=None, column=None):
"""
Return the function object of the call you're currently in.
@@ -364,27 +410,63 @@ class Script(object):
This would return an empty list..
:rtype: list of :class:`classes.CallSignature`
:rtype: list of :class:`classes.Signature`
"""
call_details = helpers.get_call_signature_details(self._module_node, self._pos)
pos = line, column
call_details = helpers.get_signature_details(self._module_node, pos)
if call_details is None:
return []
context = self._get_module_context().create_context(call_details.bracket_leaf)
definitions = helpers.cache_call_signatures(
definitions = helpers.cache_signatures(
self._inference_state,
context,
call_details.bracket_leaf,
self._code_lines,
self._pos
pos
)
debug.speed('func_call followed')
# TODO here we use stubs instead of the actual values. We should use
# the signatures from stubs, but the actual values, probably?!
return [classes.CallSignature(self._inference_state, signature, call_details)
return [classes.Signature(self._inference_state, signature, call_details)
for signature in definitions.get_signatures()]
@validate_line_column
def get_context(self, line=None, column=None):
pos = (line, column)
leaf = self._module_node.get_leaf_for_position(pos, include_prefixes=True)
if leaf.start_pos > pos or leaf.type == 'endmarker':
previous_leaf = leaf.get_previous_leaf()
if previous_leaf is not None:
leaf = previous_leaf
module_context = self._get_module_context()
n = tree.search_ancestor(leaf, 'funcdef', 'classdef')
if n is not None and n.start_pos < pos <= n.children[-1].start_pos:
# This is a bit of a special case. The context of a function/class
# name/param/keyword is always it's parent context, not the
# function itself. Catch all the cases here where we are before the
# suite object, but still in the function.
context = module_context.create_value(n).as_context()
else:
context = module_context.create_context(leaf)
while context.name is None:
context = context.parent_context # comprehensions
definition = classes.Definition(self._inference_state, context.name)
while definition.type != 'module':
name = definition._name # TODO private access
tree_name = name.tree_name
if tree_name is not None: # Happens with lambdas.
scope = tree_name.get_definition()
if scope.start_pos[1] < column:
break
definition = definition.parent()
return definition
def _analysis(self):
self._inference_state.is_analysis = True
self._inference_state.analysis_modules = [self._module_node]
@@ -408,7 +490,7 @@ class Script(object):
unpack_tuple_to_dict(context, types, testlist)
else:
if node.type == 'name':
defs = self._inference_state.goto_definitions(context, node)
defs = self._inference_state.infer(context, node)
else:
defs = infer_call_of_leaf(context, node)
try_iter_content(defs)
@@ -419,6 +501,36 @@ class Script(object):
finally:
self._inference_state.is_analysis = False
def names(self, **kwargs):
"""
Returns a list of `Definition` objects, containing name parts.
This means you can call ``Definition.goto()`` and get the
reference of a name.
:param all_scopes: If True lists the names of all scopes instead of only
the module namespace.
:param definitions: If True lists the names that have been defined by a
class, function or a statement (``a = b`` returns ``a``).
:param references: If True lists all the names that are not listed by
``definitions=True``. E.g. ``a = b`` returns ``b``.
"""
return self._names(**kwargs) # Python 2...
def _names(self, all_scopes=False, definitions=True, references=False):
def def_ref_filter(_def):
is_def = _def._name.tree_name.is_definition()
return definitions and is_def or references and not is_def
# Set line/column to a random position, because they don't matter.
module_context = self._get_module_context()
defs = [
classes.Definition(
self._inference_state,
module_context.create_name(name)
) for name in get_module_names(self._module_node, all_scopes)
]
return sorted(filter(def_ref_filter, defs), key=lambda x: (x.line, x.column))
class Interpreter(Script):
"""
@@ -432,7 +544,7 @@ class Interpreter(Script):
>>> from os.path import join
>>> namespace = locals()
>>> script = Interpreter('join("").up', [namespace])
>>> print(script.completions()[0].name)
>>> print(script.complete()[0].name)
upper
"""
_allow_descriptor_getattr_default = True
@@ -484,34 +596,17 @@ class Interpreter(Script):
def names(source=None, path=None, encoding='utf-8', all_scopes=False,
definitions=True, references=False, environment=None):
"""
Returns a list of `Definition` objects, containing name parts.
This means you can call ``Definition.goto_assignments()`` and get the
reference of a name.
The parameters are the same as in :py:class:`Script`, except or the
following ones:
warnings.warn(
"Deprecated since version 0.16.0. Use Script(...).names instead.",
DeprecationWarning,
stacklevel=2
)
:param all_scopes: If True lists the names of all scopes instead of only
the module namespace.
:param definitions: If True lists the names that have been defined by a
class, function or a statement (``a = b`` returns ``a``).
:param references: If True lists all the names that are not listed by
``definitions=True``. E.g. ``a = b`` returns ``b``.
"""
def def_ref_filter(_def):
is_def = _def._name.tree_name.is_definition()
return definitions and is_def or references and not is_def
# Set line/column to a random position, because they don't matter.
script = Script(source, line=1, column=0, path=path, encoding=encoding, environment=environment)
module_context = script._get_module_context()
defs = [
classes.Definition(
script._inference_state,
module_context.create_name(name)
) for name in get_module_names(script._module_node, all_scopes)
]
return sorted(filter(def_ref_filter, defs), key=lambda x: (x.line, x.column))
return Script(source, path=path, encoding=encoding).names(
all_scopes=all_scopes,
definitions=definitions,
references=references,
)
def preload_module(*modules):
@@ -523,7 +618,7 @@ def preload_module(*modules):
"""
for m in modules:
s = "import %s as x; x." % m
Script(s, 1, len(s), None).completions()
Script(s, path=None).complete(1, len(s))
def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,

View File

@@ -7,6 +7,8 @@ import re
import sys
import warnings
from parso.python.tree import search_ancestor
from jedi import settings
from jedi import debug
from jedi.inference.utils import unite
@@ -104,7 +106,7 @@ class BaseDefinition(object):
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:`jedi.Script.goto_definitions` should return a list of
to Jedi, :meth:`jedi.Script.infer` should return a list of
definition for ``sys``, ``f``, ``C`` and ``x``.
>>> from jedi._compatibility import no_unicode_pprint
@@ -127,7 +129,7 @@ class BaseDefinition(object):
... variable'''
>>> script = Script(source)
>>> defs = script.goto_definitions()
>>> defs = script.infer()
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.
@@ -177,7 +179,7 @@ class BaseDefinition(object):
>>> from jedi import Script
>>> source = 'import json'
>>> script = Script(source, path='example.py')
>>> d = script.goto_definitions()[0]
>>> d = script.infer()[0]
>>> print(d.module_name) # doctest: +ELLIPSIS
json
"""
@@ -217,18 +219,18 @@ class BaseDefinition(object):
... def f(a, b=1):
... "Document for function f."
... '''
>>> script = Script(source, 1, len('def f'), 'example.py')
>>> doc = script.goto_definitions()[0].docstring()
>>> script = Script(source, path='example.py')
>>> doc = script.infer(1, len('def f'))[0].docstring()
>>> print(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
docstring. For function, it is signature. If you need
actual docstring, use ``raw=True`` instead.
>>> print(script.goto_definitions()[0].docstring(raw=True))
>>> print(script.infer(1, len('def f'))[0].docstring(raw=True))
Document for function f.
:param fast: Don't follow imports that are only one level deep like
@@ -237,7 +239,9 @@ class BaseDefinition(object):
the ``foo.docstring(fast=False)`` on every object, because it
parses all libraries starting with ``a``.
"""
return _Help(self._name).docstring(fast=fast, raw=raw)
if isinstance(self._name, ImportName) and fast:
return ''
return self._name.py__doc__(include_signatures=not raw)
@property
def description(self):
@@ -259,8 +263,8 @@ class BaseDefinition(object):
>>> source = '''
... import os
... os.path.join'''
>>> script = Script(source, 3, len('os.path.join'), 'example.py')
>>> print(script.goto_definitions()[0].full_name)
>>> script = Script(source, path='example.py')
>>> print(script.infer(3, len('os.path.join'))[0].full_name)
os.path.join
Notice that it returns ``'os.path.join'`` instead of (for example)
@@ -289,11 +293,19 @@ class BaseDefinition(object):
return self._name.get_root_context().is_stub()
def goto_assignments(self, **kwargs): # Python 2...
def goto(self, **kwargs):
with debug.increase_indent_cm('goto for %s' % self._name):
return self._goto_assignments(**kwargs)
return self._goto(**kwargs)
def _goto_assignments(self, only_stubs=False, prefer_stubs=False):
def goto_assignments(self, **kwargs): # Python 2...
warnings.warn(
"Deprecated since version 0.16.0. Use .goto.",
DeprecationWarning,
stacklevel=2
)
return self.goto(**kwargs)
def _goto(self, only_stubs=False, prefer_stubs=False):
assert not (only_stubs and prefer_stubs)
if not self._name.is_value_name:
@@ -358,12 +370,28 @@ class BaseDefinition(object):
if not self._name.is_value_name:
return None
context = self._name.parent_context
if self.type in ('function', 'class', 'param') and self._name.tree_name is not None:
# Since the parent_context doesn't really match what the user
# thinks of that the parent is here, we do these cases separately.
# The reason for this is the following:
# - class: Nested classes parent_context is always the
# parent_context of the most outer one.
# - function: Functions in classes have the module as
# parent_context.
# - param: The parent_context of a param is not its function but
# e.g. the outer class or module.
cls_or_func_node = self._name.tree_name.get_definition()
parent = search_ancestor(cls_or_func_node, 'funcdef', 'classdef', 'file_input')
context = self._get_module_context().create_value(parent).as_context()
else:
context = self._name.parent_context
if context is None:
return None
while context.name is None:
# Happens for comprehension contexts
context = context.parent_context
return Definition(self._inference_state, context.name)
def __repr__(self):
@@ -384,17 +412,23 @@ class BaseDefinition(object):
:return str: Returns the line(s) of code or an empty string if it's a
builtin.
"""
if not self._name.is_value_name or self.in_builtin_module():
if not self._name.is_value_name:
return ''
lines = self._name.get_root_context().code_lines
if lines is None:
# Probably a builtin module, just ignore in that case.
return ''
index = self._name.start_pos[0] - 1
start_index = max(index - before, 0)
return ''.join(lines[start_index:index + after + 1])
def get_signatures(self):
return [Signature(self._inference_state, s) for s in self._name.infer().get_signatures()]
return [
BaseSignature(self._inference_state, s)
for s in self._name.infer().get_signatures()
]
def execute(self):
return _values_to_definitions(self._name.infer().execute_with_values())
@@ -402,14 +436,15 @@ class BaseDefinition(object):
class Completion(BaseDefinition):
"""
`Completion` objects are returned from :meth:`api.Script.completions`. They
`Completion` objects are returned from :meth:`api.Script.complete`. They
provide additional information about a completion.
"""
def __init__(self, inference_state, name, stack, like_name_length):
def __init__(self, inference_state, name, stack, like_name_length, is_fuzzy):
super(Completion, self).__init__(inference_state, name)
self._like_name_length = like_name_length
self._stack = stack
self._is_fuzzy = is_fuzzy
# Completion objects with the same Completion name (which means
# duplicate items in the completion)
@@ -435,6 +470,9 @@ class Completion(BaseDefinition):
@property
def complete(self):
"""
Only works with non-fuzzy completions. Returns None if fuzzy
completions are used.
Return the rest of the word, e.g. completing ``isinstance``::
isinstan# <-- Cursor is here
@@ -449,9 +487,9 @@ class Completion(BaseDefinition):
completing ``foo(par`` would give a ``Completion`` which `complete`
would be `am=`
"""
if self._is_fuzzy:
return None
return self._complete(True)
@property
@@ -507,8 +545,8 @@ class Completion(BaseDefinition):
class Definition(BaseDefinition):
"""
*Definition* objects are returned from :meth:`api.Script.goto_assignments`
or :meth:`api.Script.goto_definitions`.
*Definition* objects are returned from :meth:`api.Script.goto`
or :meth:`api.Script.infer`.
"""
def __init__(self, inference_state, definition):
super(Definition, self).__init__(inference_state, definition)
@@ -531,8 +569,8 @@ class Definition(BaseDefinition):
... pass
...
... variable = f if random.choice([0,1]) else C'''
>>> script = Script(source, column=3) # line is maximum by default
>>> defs = script.goto_definitions()
>>> script = Script(source) # line is maximum by default
>>> defs = script.infer(column=3)
>>> defs = sorted(defs, key=lambda d: d.line)
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE
[<Definition full_name='__main__.f', description='def f'>,
@@ -613,14 +651,14 @@ class Definition(BaseDefinition):
return hash((self._name.start_pos, self.module_path, self.name, self._inference_state))
class Signature(Definition):
class BaseSignature(Definition):
"""
`Signature` objects is the return value of `Script.function_definition`.
`BaseSignature` 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, inference_state, signature):
super(Signature, self).__init__(inference_state, signature.name)
super(BaseSignature, self).__init__(inference_state, signature.name)
self._signature = signature
@property
@@ -635,15 +673,15 @@ class Signature(Definition):
return self._signature.to_string()
class CallSignature(Signature):
class Signature(BaseSignature):
"""
`CallSignature` objects is the return value of `Script.call_signatures`.
`Signature` objects is the return value of `Script.find_signatures`.
It knows what functions you are currently in. e.g. `isinstance(` would
return the `isinstance` function with its params. Without `(` it would
return nothing.
"""
def __init__(self, inference_state, signature, call_details):
super(CallSignature, self).__init__(inference_state, signature)
super(Signature, self).__init__(inference_state, signature)
self._call_details = call_details
self._signature = signature
@@ -712,55 +750,3 @@ def _format_signatures(value):
signature.to_string()
for signature in value.get_signatures()
)
class _Help(object):
"""
Temporary implementation, will be used as `Script.help() or something in
the future.
"""
def __init__(self, definition):
self._name = definition
@memoize_method
def _get_values(self, fast):
if isinstance(self._name, ImportName) and fast:
return {}
if self._name.api_type == 'statement':
return {}
return self._name.infer()
def docstring(self, fast=True, raw=True):
"""
The docstring ``__doc__`` for any object.
See :attr:`doc` for example.
"""
full_doc = ''
# Using the first docstring that we see.
for value in self._get_values(fast=fast):
if full_doc:
# In case we have multiple values, just return all of them
# separated by a few dashes.
full_doc += '\n' + '-' * 30 + '\n'
doc = value.py__doc__()
signature_text = ''
if self._name.is_value_name:
if not raw:
signature_text = _format_signatures(value)
if not doc and value.is_stub():
for c in convert_values(ValueSet({value}), ignore_compiled=False):
doc = c.py__doc__()
if doc:
break
if signature_text and doc:
full_doc += signature_text + '\n\n' + doc
else:
full_doc += signature_text + doc
return full_doc

View File

@@ -11,17 +11,20 @@ from jedi.api import classes
from jedi.api import helpers
from jedi.api import keywords
from jedi.api.strings import completions_for_dicts
from jedi.api.file_name import file_name_completions
from jedi.api.file_name import complete_file_name
from jedi.inference import imports
from jedi.inference.base_value import ValueSet
from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names
from jedi.inference.context import get_global_filters
from jedi.inference.value import TreeInstance
from jedi.inference.gradual.conversion import convert_values
from jedi.parser_utils import cut_value_at_position
from jedi.plugins import plugin_manager
def get_call_signature_param_names(call_signatures):
def get_signature_param_names(signatures):
# add named params
for call_sig in call_signatures:
for call_sig in signatures:
for p in call_sig.params:
# Allow protected access, because it's a public API.
if p._name.get_kind() in (Parameter.POSITIONAL_OR_KEYWORD,
@@ -29,7 +32,7 @@ def get_call_signature_param_names(call_signatures):
yield p._name
def filter_names(inference_state, completion_names, stack, like_name):
def filter_names(inference_state, completion_names, stack, like_name, fuzzy):
comp_dct = {}
if settings.case_insensitive_completion:
like_name = like_name.lower()
@@ -37,13 +40,17 @@ def filter_names(inference_state, completion_names, stack, like_name):
string = name.string_name
if settings.case_insensitive_completion:
string = string.lower()
if string.startswith(like_name):
if fuzzy:
match = helpers.fuzzy_match(string, like_name)
else:
match = helpers.start_match(string, like_name)
if match:
new = classes.Completion(
inference_state,
name,
stack,
len(like_name)
len(like_name),
is_fuzzy=fuzzy,
)
k = (new.name, new.complete) # key
if k in comp_dct and settings.no_completion_duplicates:
@@ -69,9 +76,16 @@ def get_flow_scope_node(module_node, position):
return node
@plugin_manager.decorate()
def complete_param_names(context, function_name, decorator_nodes):
# Basically there's no way to do param completion. The plugins are
# responsible for this.
return []
class Completion:
def __init__(self, inference_state, module_context, code_lines, position,
call_signatures_callback):
signatures_callback, fuzzy=False):
self._inference_state = inference_state
self._module_context = module_context
self._module_node = module_context.tree_node
@@ -83,19 +97,21 @@ class Completion:
# everything. We want the start of the name we're on.
self._original_position = position
self._position = position[0], position[1] - len(self._like_name)
self._call_signatures_callback = call_signatures_callback
self._signatures_callback = signatures_callback
def completions(self):
self._fuzzy = fuzzy
def complete(self, fuzzy):
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
string, start_leaf = _extract_string_while_in_string(leaf, self._position)
prefixed_completions = []
if string is None:
string = ''
#if string is None:
#string = ''
bracket_leaf = leaf
if bracket_leaf.type in ('number', 'error_leaf'):
string = bracket_leaf.value
bracket_leaf = bracket_leaf.get_previous_leaf()
#if bracket_leaf.type in ('number', 'error_leaf'):
#string = bracket_leaf.value
#bracket_leaf = bracket_leaf.get_previous_leaf()
if bracket_leaf == '[':
context = self._module_context.create_context(bracket_leaf)
@@ -103,23 +119,24 @@ class Completion:
if before_bracket_leaf.type in ('atom', 'trailer', 'name'):
values = infer_call_of_leaf(context, before_bracket_leaf)
prefixed_completions += completions_for_dicts(
self._inference_state, values, string)
self._inference_state, values, string, fuzzy=fuzzy)
if string is not None and not prefixed_completions:
completions = list(file_name_completions(
prefixed_completions = list(complete_file_name(
self._inference_state, self._module_context, start_leaf, string,
self._like_name, self._call_signatures_callback,
self._code_lines, self._original_position
self._like_name, self._signatures_callback,
self._code_lines, self._original_position,
fuzzy
))
if completions:
return completions
if prefixed_completions:
return prefixed_completions
if string is not None:
return prefixed_completions
completion_names = self._get_context_completions(leaf)
completion_names = self._complete_python(leaf)
completions = filter_names(self._inference_state, completion_names,
self.stack, self._like_name)
self.stack, self._like_name, fuzzy)
return (
prefixed_completions +
@@ -128,7 +145,7 @@ class Completion:
x.name.lower()))
)
def _get_context_completions(self, leaf):
def _complete_python(self, leaf):
"""
Analyzes the current context of a completion and decides what to
return.
@@ -158,7 +175,7 @@ class Completion:
return []
# If we don't have a value, just use global completion.
return self._global_completions()
return self._complete_global_scope()
allowed_transitions = \
list(stack._allowed_transition_names_and_token_types())
@@ -197,8 +214,9 @@ class Completion:
completion_names = []
current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
if not current_line or current_line[-1] in ' \t.;':
completion_names += self._get_keyword_completion_names(allowed_transitions)
if not current_line or current_line[-1] in ' \t.;' \
and current_line[-3:] != '...':
completion_names += self._complete_keywords(allowed_transitions)
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
PythonTokenTypes.INDENT)):
@@ -210,7 +228,7 @@ class Completion:
if nodes and nodes[-1] in ('as', 'def', 'class'):
# No completions for ``with x as foo`` and ``import x as foo``.
# Also true for defining names as a class or function.
return list(self._get_class_value_completions(is_function=True))
return list(self._complete_inherited(is_function=True))
elif "import_stmt" in nonterminals:
level, names = parse_dotted_names(nodes, "import_from" in nonterminals)
@@ -222,23 +240,68 @@ class Completion:
)
elif nonterminals[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.':
dot = self._module_node.get_leaf_for_position(self._position)
completion_names += self._trailer_completions(dot.get_previous_leaf())
completion_names += self._complete_trailer(dot.get_previous_leaf())
elif self._is_parameter_completion():
completion_names += self._complete_params(leaf)
else:
completion_names += self._global_completions()
completion_names += self._get_class_value_completions(is_function=False)
completion_names += self._complete_global_scope()
completion_names += self._complete_inherited(is_function=False)
if 'trailer' in nonterminals:
call_signatures = self._call_signatures_callback()
completion_names += get_call_signature_param_names(call_signatures)
# Apparently this looks like it's good enough to filter most cases
# so that signature completions don't randomly appear.
# To understand why this works, three things are important:
# 1. trailer with a `,` in it is either a subscript or an arglist.
# 2. If there's no `,`, it's at the start and only signatures start
# with `(`. Other trailers could start with `.` or `[`.
# 3. Decorators are very primitive and have an optional `(` with
# optional arglist in them.
if nodes[-1] in ['(', ','] and nonterminals[-1] in ('trailer', 'arglist', 'decorator'):
signatures = self._signatures_callback(*self._position)
completion_names += get_signature_param_names(signatures)
return completion_names
def _get_keyword_completion_names(self, allowed_transitions):
def _is_parameter_completion(self):
tos = self.stack[-1]
if tos.nonterminal == 'lambdef' and len(tos.nodes) == 1:
# We are at the position `lambda `, where basically the next node
# is a param.
return True
if tos.nonterminal in 'parameters':
# Basically we are at the position `foo(`, there's nothing there
# yet, so we have no `typedargslist`.
return True
# var args is for lambdas and typed args for normal functions
return tos.nonterminal in ('typedargslist', 'varargslist') and tos.nodes[-1] == ','
def _complete_params(self, leaf):
stack_node = self.stack[-2]
if stack_node.nonterminal == 'parameters':
stack_node = self.stack[-3]
if stack_node.nonterminal == 'funcdef':
context = get_user_context(self._module_context, self._position)
node = search_ancestor(leaf, 'error_node', 'funcdef')
if node.type == 'error_node':
n = node.children[0]
if n.type == 'decorators':
decorators = n.children
elif n.type == 'decorator':
decorators = [n]
else:
decorators = []
else:
decorators = node.get_decorators()
function_name = stack_node.nodes[1]
return complete_param_names(context, function_name.value, decorators)
return []
def _complete_keywords(self, allowed_transitions):
for k in allowed_transitions:
if isinstance(k, str) and k.isalpha():
yield keywords.KeywordName(self._inference_state, k)
def _global_completions(self):
def _complete_global_scope(self):
context = get_user_context(self._module_context, self._position)
debug.dbg('global completion scope: %s', context)
flow_scope_node = get_flow_scope_node(self._module_node, self._position)
@@ -252,16 +315,22 @@ class Completion:
completion_names += filter.values()
return completion_names
def _trailer_completions(self, previous_leaf):
user_value = get_user_context(self._module_context, self._position)
def _complete_trailer(self, previous_leaf):
inferred_context = self._module_context.create_context(previous_leaf)
values = infer_call_of_leaf(inferred_context, previous_leaf)
completion_names = []
debug.dbg('trailer completion values: %s', values, color='MAGENTA')
return self._complete_trailer_for_values(values)
def _complete_trailer_for_values(self, values):
user_value = get_user_context(self._module_context, self._position)
completion_names = []
for value in values:
for filter in value.get_filters(origin_scope=user_value.tree_node):
completion_names += filter.values()
if not value.is_stub() and isinstance(value, TreeInstance):
completion_names += self._complete_getattr(value)
python_values = convert_values(values)
for c in python_values:
if c not in values:
@@ -269,12 +338,72 @@ class Completion:
completion_names += filter.values()
return completion_names
def _complete_getattr(self, instance):
"""
A heuristic to make completion for proxy objects work. This is not
intended to work in all cases. It works exactly in this case:
def __getattr__(self, name):
...
return getattr(any_object, name)
It is important that the return contains getattr directly, otherwise it
won't work anymore. It's really just a stupid heuristic. It will not
work if you write e.g. `return (getatr(o, name))`, because of the
additional parentheses. It will also not work if you move the getattr
to some other place that is not the return statement itself.
It is intentional that it doesn't work in all cases. Generally it's
really hard to do even this case (as you can see below). Most people
will write it like this anyway and the other ones, well they are just
out of luck I guess :) ~dave.
"""
names = (instance.get_function_slot_names(u'__getattr__')
or instance.get_function_slot_names(u'__getattribute__'))
functions = ValueSet.from_sets(
name.infer()
for name in names
)
for func in functions:
tree_node = func.tree_node
for return_stmt in tree_node.iter_return_stmts():
# Basically until the next comment we just try to find out if a
# return statement looks exactly like `return getattr(x, name)`.
if return_stmt.type != 'return_stmt':
continue
atom_expr = return_stmt.children[1]
if atom_expr.type != 'atom_expr':
continue
atom = atom_expr.children[0]
trailer = atom_expr.children[1]
if len(atom_expr.children) != 2 or atom.type != 'name' \
or atom.value != 'getattr':
continue
arglist = trailer.children[1]
if arglist.type != 'arglist' or len(arglist.children) < 3:
continue
context = func.as_context()
object_node = arglist.children[0]
# Make sure it's a param: foo in __getattr__(self, foo)
name_node = arglist.children[2]
name_list = context.goto(name_node, name_node.start_pos)
if not any(n.api_type == 'param' for n in name_list):
continue
# Now that we know that these are most probably completion
# objects, we just infer the object and return them as
# completions.
objects = context.infer_node(object_node)
return self._complete_trailer_for_values(objects)
return []
def _get_importer_names(self, names, level=0, only_modules=True):
names = [n.value for n in names]
i = imports.Importer(self._inference_state, names, self._module_context, level)
return i.completion_names(self._inference_state, only_modules=only_modules)
def _get_class_value_completions(self, is_function=True):
def _complete_inherited(self, is_function=True):
"""
Autocomplete inherited methods when overriding in child class.
"""
@@ -310,6 +439,9 @@ def _gather_nodes(stack):
def _extract_string_while_in_string(leaf, position):
if position < leaf.start_pos:
return None, None
if leaf.type == 'string':
match = re.match(r'^\w*(\'{3}|"{3}|\'|")', leaf.value)
quote = match.group(1)

View File

@@ -19,6 +19,7 @@ _VersionInfo = namedtuple('VersionInfo', 'major minor micro')
_SUPPORTED_PYTHONS = ['3.8', '3.7', '3.6', '3.5', '3.4', '2.7']
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
_CONDA_VAR = 'CONDA_PREFIX'
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
@@ -147,13 +148,13 @@ class InterpreterEnvironment(_SameEnvironmentMixin, _BaseEnvironment):
return sys.path
def _get_virtual_env_from_var():
def _get_virtual_env_from_var(env_var='VIRTUAL_ENV'):
"""Get virtualenv environment from VIRTUAL_ENV environment variable.
It uses `safe=False` with ``create_environment``, because the environment
variable is considered to be safe / controlled by the user solely.
"""
var = os.environ.get('VIRTUAL_ENV')
var = os.environ.get(env_var)
if var:
# Under macOS in some cases - notably when using Pipenv - the
# sys.prefix of the virtualenv is /path/to/env/bin/.. instead of
@@ -178,7 +179,8 @@ def _calculate_sha256_for_file(path):
def get_default_environment():
"""
Tries to return an active Virtualenv. If there is no VIRTUAL_ENV variable
Tries to return an active Virtualenv or conda environment.
If there is no VIRTUAL_ENV variable or no CONDA_PREFIX variable set
set it will return the latest Python version installed on the system. This
makes it possible to use as many new Python features as possible when using
autocompletion and other functionality.
@@ -189,6 +191,10 @@ def get_default_environment():
if virtual_env is not None:
return virtual_env
conda_env = _get_virtual_env_from_var(_CONDA_VAR)
if conda_env is not None:
return conda_env
return _try_get_same_env()
@@ -233,7 +239,7 @@ def _try_get_same_env():
def get_cached_default_environment():
var = os.environ.get('VIRTUAL_ENV')
var = os.environ.get('VIRTUAL_ENV') or os.environ.get(_CONDA_VAR)
environment = _get_cached_default_environment()
# Under macOS in some cases - notably when using Pipenv - the
@@ -255,28 +261,37 @@ def find_virtualenvs(paths=None, **kwargs):
"""
:param paths: A list of paths in your file system to be scanned for
Virtualenvs. It will search in these paths and potentially execute the
Python binaries. Also the VIRTUAL_ENV variable will be checked if it
contains a valid Virtualenv.
Python binaries.
:param safe: Default True. In case this is False, it will allow this
function to execute potential `python` environments. An attacker might
be able to drop an executable in a path this function is searching by
default. If the executable has not been installed by root, it will not
be executed.
:param use_environment_vars: Default True. If True, the VIRTUAL_ENV
variable will be checked if it contains a valid VirtualEnv.
CONDA_PREFIX will be checked to see if it contains a valid conda
environment.
:yields: :class:`Environment`
"""
def py27_comp(paths=None, safe=True):
def py27_comp(paths=None, safe=True, use_environment_vars=True):
if paths is None:
paths = []
_used_paths = set()
# Using this variable should be safe, because attackers might be able
# to drop files (via git) but not environment variables.
virtual_env = _get_virtual_env_from_var()
if virtual_env is not None:
yield virtual_env
_used_paths.add(virtual_env.path)
if use_environment_vars:
# Using this variable should be safe, because attackers might be
# able to drop files (via git) but not environment variables.
virtual_env = _get_virtual_env_from_var()
if virtual_env is not None:
yield virtual_env
_used_paths.add(virtual_env.path)
conda_env = _get_virtual_env_from_var(_CONDA_VAR)
if conda_env is not None:
yield conda_env
_used_paths.add(conda_env.path)
for directory in paths:
if not os.path.isdir(directory):

View File

@@ -3,6 +3,7 @@ import os
from jedi._compatibility import FileNotFoundError, force_unicode, scandir
from jedi.api import classes
from jedi.api.strings import StringName, get_quote_ending
from jedi.api.helpers import fuzzy_match, start_match
from jedi.inference.helpers import get_str_or_none
@@ -10,8 +11,8 @@ class PathName(StringName):
api_type = u'path'
def file_name_completions(inference_state, module_context, start_leaf, string,
like_name, call_signatures_callback, code_lines, position):
def complete_file_name(inference_state, module_context, start_leaf, string,
like_name, signatures_callback, code_lines, position, fuzzy):
# First we want to find out what can actually be changed as a name.
like_name_length = len(os.path.basename(string) + like_name)
@@ -25,7 +26,7 @@ def file_name_completions(inference_state, module_context, start_leaf, string,
must_start_with = os.path.basename(string) + like_name
string = os.path.dirname(string)
sigs = call_signatures_callback()
sigs = signatures_callback(*position)
is_in_os_path_join = sigs and all(s.full_name == 'os.path.join' for s in sigs)
if is_in_os_path_join:
to_be_added = _add_os_path_join(module_context, start_leaf, sigs[0].bracket_start)
@@ -35,12 +36,17 @@ def file_name_completions(inference_state, module_context, start_leaf, string,
string = to_be_added + string
base_path = os.path.join(inference_state.project._path, string)
try:
listed = scandir(base_path)
except FileNotFoundError:
listed = sorted(scandir(base_path), key=lambda e: e.name)
# OSError: [Errno 36] File name too long: '...'
except (FileNotFoundError, OSError):
return
for entry in listed:
name = entry.name
if name.startswith(must_start_with):
if fuzzy:
match = fuzzy_match(name, must_start_with)
else:
match = start_match(name, must_start_with)
if match:
if is_in_os_path_join or not entry.is_dir():
name += get_quote_ending(start_leaf, code_lines, position)
else:
@@ -50,7 +56,8 @@ def file_name_completions(inference_state, module_context, start_leaf, string,
inference_state,
PathName(inference_state, name[len(must_start_with) - like_name_length:]),
stack=None,
like_name_length=like_name_length
like_name_length=like_name_length,
is_fuzzy=fuzzy,
)

View File

@@ -4,6 +4,7 @@ Helpers for the API
import re
from collections import namedtuple
from textwrap import dedent
from functools import wraps
from parso.python.parser import Parser
from parso.python import tree
@@ -13,12 +14,25 @@ from jedi.inference.base_value import NO_VALUES
from jedi.inference.syntax_tree import infer_atom
from jedi.inference.helpers import infer_call_of_leaf
from jedi.inference.compiled import get_string_value_set
from jedi.cache import call_signature_time_cache
from jedi.cache import signature_time_cache
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
def start_match(string, like_name):
return string.startswith(like_name)
def fuzzy_match(string, like_name):
if len(like_name) <= 1:
return like_name in string
pos = string.find(like_name[0])
if pos >= 0:
return fuzzy_match(string[pos + 1:], like_name[1:])
return False
def sorted_definitions(defs):
# Note: `or ''` below is required because `module_path` could be
return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0, x.name))
@@ -136,11 +150,9 @@ def get_stack_at_position(grammar, code_lines, leaf, pos):
)
def infer_goto_definition(inference_state, context, leaf):
def infer(inference_state, context, leaf):
if leaf.type == 'name':
# In case of a name we can just use goto_definition which does all the
# magic itself.
return inference_state.goto_definitions(context, leaf)
return inference_state.infer(context, leaf)
parent = leaf.parent
definitions = NO_VALUES
@@ -314,7 +326,7 @@ def _get_index_and_key(nodes, position):
return nodes_before.count(','), key_str
def _get_call_signature_details_from_error_node(node, additional_children, position):
def _get_signature_details_from_error_node(node, additional_children, position):
for index, element in reversed(list(enumerate(node.children))):
# `index > 0` means that it's a trailer and not an atom.
if element == '(' and element.end_pos <= position and index > 0:
@@ -328,33 +340,30 @@ def _get_call_signature_details_from_error_node(node, additional_children, posit
return CallDetails(element, children + additional_children, position)
def get_call_signature_details(module, position):
def get_signature_details(module, position):
leaf = module.get_leaf_for_position(position, include_prefixes=True)
# It's easier to deal with the previous token than the next one in this
# case.
if leaf.start_pos >= position:
# Whitespace / comments after the leaf count towards the previous leaf.
leaf = leaf.get_previous_leaf()
if leaf is None:
return None
if leaf == ')':
# TODO is this ok?
if leaf.end_pos == position:
leaf = leaf.get_next_leaf()
# Now that we know where we are in the syntax tree, we start to look at
# parents for possible function definitions.
node = leaf.parent
while node is not None:
if node.type in ('funcdef', 'classdef'):
# Don't show call signatures if there's stuff before it that just
# makes it feel strange to have a call signature.
# Don't show signatures if there's stuff before it that just
# makes it feel strange to have a signature.
return None
additional_children = []
for n in reversed(node.children):
if n.start_pos < position:
if n.type == 'error_node':
result = _get_call_signature_details_from_error_node(
result = _get_signature_details_from_error_node(
n, additional_children, position
)
if result is not None:
@@ -364,19 +373,25 @@ def get_call_signature_details(module, position):
continue
additional_children.insert(0, n)
# Find a valid trailer
if node.type == 'trailer' and node.children[0] == '(':
leaf = node.get_previous_leaf()
if leaf is None:
return None
return CallDetails(node.children[0], node.children, position)
# Additionally we have to check that an ending parenthesis isn't
# interpreted wrong. There are two cases:
# 1. Cursor before paren -> The current signature is good
# 2. Cursor after paren -> We need to skip the current signature
if not (leaf is node.children[-1] and position >= leaf.end_pos):
leaf = node.get_previous_leaf()
if leaf is None:
return None
return CallDetails(node.children[0], node.children, position)
node = node.parent
return None
@call_signature_time_cache("call_signatures_validity")
def cache_call_signatures(inference_state, context, bracket_leaf, code_lines, user_pos):
@signature_time_cache("call_signatures_validity")
def cache_signatures(inference_state, context, bracket_leaf, code_lines, user_pos):
"""This function calculates the cache key."""
line_index = user_pos[0] - 1
@@ -390,8 +405,31 @@ def cache_call_signatures(inference_state, context, bracket_leaf, code_lines, us
yield None # Don't cache!
else:
yield (module_path, before_bracket, bracket_leaf.start_pos)
yield infer_goto_definition(
yield infer(
inference_state,
context,
bracket_leaf.get_previous_leaf(),
)
def validate_line_column(func):
@wraps(func)
def wrapper(self, line=None, column=None, *args, **kwargs):
line = max(len(self._code_lines), 1) if line is None else line
if not (0 < line <= len(self._code_lines)):
raise ValueError('`line` parameter is not in a valid range.')
line_string = self._code_lines[line - 1]
line_len = len(line_string)
if line_string.endswith('\r\n'):
line_len -= 1
if line_string.endswith('\n'):
line_len -= 1
column = line_len if column is None else column
if not (0 <= column <= line_len):
raise ValueError('`column` parameter (%d) is not in a valid range '
'(0-%d) for line %d (%r).' % (
column, line_len, line, line_string))
return func(self, line, column, *args, **kwargs)
return wrapper

View File

@@ -15,41 +15,11 @@ except ImportError:
pydoc_topics = None
def get_operator(inference_state, string, pos):
return Keyword(inference_state, string, pos)
class KeywordName(AbstractArbitraryName):
api_type = u'keyword'
def infer(self):
return [Keyword(self.inference_state, self.string_name, (0, 0))]
class Keyword(object):
api_type = u'keyword'
def __init__(self, inference_state, name, pos):
self.name = KeywordName(inference_state, name)
self.start_pos = pos
self.parent = inference_state.builtins_module
@property
def names(self):
""" For a `parsing.Name` like comparision """
return [self.name]
def py__doc__(self):
return imitate_pydoc(self.name.string_name)
def get_signatures(self):
# TODO this makes no sense, I think Keyword should somehow merge with
# Value to make it easier for the api/classes.py to deal with all
# of it.
return []
def __repr__(self):
return '<%s: %s>' % (type(self).__name__, self.name)
def py__doc__(self, include_signatures=False):
return imitate_pydoc(self.string_name)
def imitate_pydoc(string):

View File

@@ -94,7 +94,8 @@ class Project(object):
return sys_path
@inference_state_as_method_param_cache()
def _get_sys_path(self, inference_state, environment=None, add_parent_paths=True):
def _get_sys_path(self, inference_state, environment=None,
add_parent_paths=True, add_init_paths=False):
"""
Keep this method private for all users of jedi. However internally this
one is used like a public method.
@@ -110,7 +111,17 @@ class Project(object):
suffixed += discover_buildout_paths(inference_state, inference_state.script_path)
if add_parent_paths:
traversed = list(traverse_parents(inference_state.script_path))
# Collect directories in upward search by:
# 1. Skipping directories with __init__.py
# 2. Stopping immediately when above self._path
traversed = []
for parent_path in traverse_parents(inference_state.script_path):
if not parent_path.startswith(self._path):
break
if not add_init_paths \
and os.path.isfile(os.path.join(parent_path, "__init__.py")):
continue
traversed.append(parent_path)
# AFAIK some libraries have imports like `foo.foo.bar`, which
# leads to the conclusion to by default prefer longer paths

View File

@@ -21,7 +21,7 @@ import jedi.utils
from jedi import __version__ as __jedi_version__
print('REPL completion using Jedi %s' % __jedi_version__)
jedi.utils.setup_readline()
jedi.utils.setup_readline(fuzzy=False)
del jedi

View File

@@ -19,12 +19,18 @@ class StringName(AbstractArbitraryName):
is_value_name = False
def completions_for_dicts(inference_state, dicts, literal_string):
def completions_for_dicts(inference_state, dicts, literal_string, fuzzy):
for dict_key in sorted(_get_python_keys(dicts)):
dict_key_str = repr(dict_key)
if dict_key_str.startswith(literal_string):
name = StringName(inference_state, dict_key_str[len(literal_string):])
yield Completion(inference_state, name, stack=None, like_name_length=0)
yield Completion(
inference_state,
name,
stack=None,
like_name_length=0,
is_fuzzy=fuzzy
)
def _get_python_keys(dicts):