forked from VimPlug/jedi
Implement a search function, fixes #225
This commit is contained in:
@@ -26,7 +26,7 @@ from jedi.api import classes
|
|||||||
from jedi.api import interpreter
|
from jedi.api import interpreter
|
||||||
from jedi.api import helpers
|
from jedi.api import helpers
|
||||||
from jedi.api.helpers import validate_line_column
|
from jedi.api.helpers import validate_line_column
|
||||||
from jedi.api.completion import Completion
|
from jedi.api.completion import Completion, complete_trailer
|
||||||
from jedi.api.keywords import KeywordName
|
from jedi.api.keywords import KeywordName
|
||||||
from jedi.api.environment import InterpreterEnvironment
|
from jedi.api.environment import InterpreterEnvironment
|
||||||
from jedi.api.project import get_default_project, Project
|
from jedi.api.project import get_default_project, Project
|
||||||
@@ -45,6 +45,7 @@ from jedi.inference.base_value import ValueSet
|
|||||||
from jedi.inference.value.iterable import unpack_tuple_to_dict
|
from jedi.inference.value.iterable import unpack_tuple_to_dict
|
||||||
from jedi.inference.gradual.conversion import convert_names, convert_values
|
from jedi.inference.gradual.conversion import convert_names, convert_values
|
||||||
from jedi.inference.gradual.utils import load_proper_stub_module
|
from jedi.inference.gradual.utils import load_proper_stub_module
|
||||||
|
from jedi.inference.utils import to_list
|
||||||
|
|
||||||
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
|
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
|
||||||
# can remove some "maximum recursion depth" errors.
|
# can remove some "maximum recursion depth" errors.
|
||||||
@@ -336,6 +337,43 @@ class Script(object):
|
|||||||
defs = [classes.Definition(self._inference_state, d) for d in set(names)]
|
defs = [classes.Definition(self._inference_state, d) for d in set(names)]
|
||||||
return helpers.sorted_definitions(defs)
|
return helpers.sorted_definitions(defs)
|
||||||
|
|
||||||
|
def search(self, name, **kwargs):
|
||||||
|
"""
|
||||||
|
Searches a symbol in the current file.
|
||||||
|
|
||||||
|
:param all_scopes: If True lists the symbols of all scopes instead of
|
||||||
|
only the module.
|
||||||
|
: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._search(name, **kwargs) # Python 2 ...
|
||||||
|
|
||||||
|
@to_list
|
||||||
|
def _search(self, line, column, name, complete=False, all_scopes=False,
|
||||||
|
fuzzy=False):
|
||||||
|
wanted_type, wanted_names = helpers.split_search_string(name)
|
||||||
|
|
||||||
|
names = [d._name for d in self._names(all_scopes=all_scopes)]
|
||||||
|
for s in wanted_names[:-1]:
|
||||||
|
new_names = []
|
||||||
|
for n in names:
|
||||||
|
if s == n.string_name:
|
||||||
|
new_names += complete_trailer(
|
||||||
|
self._get_module_context(),
|
||||||
|
n.infer()
|
||||||
|
)
|
||||||
|
names = new_names
|
||||||
|
|
||||||
|
last_name = wanted_names[-1]
|
||||||
|
for n in names:
|
||||||
|
if complete and helpers.match(n.string_name, last_name, fuzzy=fuzzy) \
|
||||||
|
or not complete and n.string_name == last_name:
|
||||||
|
def_ = classes.Definition(self._inference_state, n)
|
||||||
|
if wanted_type is None or wanted_type == def_.api_type:
|
||||||
|
yield def_
|
||||||
|
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def help(self, line=None, column=None):
|
def help(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -357,80 +357,7 @@ class Completion:
|
|||||||
def _complete_trailer_for_values(self, values):
|
def _complete_trailer_for_values(self, values):
|
||||||
user_context = get_user_context(self._module_context, self._position)
|
user_context = get_user_context(self._module_context, self._position)
|
||||||
|
|
||||||
completion_names = []
|
return complete_trailer(user_context, values)
|
||||||
for value in values:
|
|
||||||
for filter in value.get_filters(origin_scope=user_context.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:
|
|
||||||
for filter in c.get_filters(origin_scope=user_context.tree_node):
|
|
||||||
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):
|
def _get_importer_names(self, names, level=0, only_modules=True):
|
||||||
names = [n.value for n in names]
|
names = [n.value for n in names]
|
||||||
@@ -568,3 +495,81 @@ def _extract_string_while_in_string(leaf, position):
|
|||||||
leaves.insert(0, leaf)
|
leaves.insert(0, leaf)
|
||||||
leaf = leaf.get_previous_leaf()
|
leaf = leaf.get_previous_leaf()
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
|
||||||
|
|
||||||
|
def complete_trailer(user_context, values):
|
||||||
|
completion_names = []
|
||||||
|
for value in values:
|
||||||
|
for filter in value.get_filters(origin_scope=user_context.tree_node):
|
||||||
|
completion_names += filter.values()
|
||||||
|
|
||||||
|
if not value.is_stub() and isinstance(value, TreeInstance):
|
||||||
|
completion_names += _complete_getattr(user_context, value)
|
||||||
|
|
||||||
|
python_values = convert_values(values)
|
||||||
|
for c in python_values:
|
||||||
|
if c not in values:
|
||||||
|
for filter in c.get_filters(origin_scope=user_context.tree_node):
|
||||||
|
completion_names += filter.values()
|
||||||
|
return completion_names
|
||||||
|
|
||||||
|
|
||||||
|
def _complete_getattr(user_context, 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 complete_trailer(user_context, objects)
|
||||||
|
return []
|
||||||
|
|||||||
@@ -462,3 +462,8 @@ def validate_line_column(func):
|
|||||||
column, line_len, line, line_string))
|
column, line_len, line, line_string))
|
||||||
return func(self, line, column, *args, **kwargs)
|
return func(self, line, column, *args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def split_search_string(name):
|
||||||
|
type, _, dotted_names = name.rpartition(' ')
|
||||||
|
return type, dotted_names.split('.')
|
||||||
|
|||||||
Reference in New Issue
Block a user