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 helpers
|
||||
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.environment import InterpreterEnvironment
|
||||
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.gradual.conversion import convert_names, convert_values
|
||||
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
|
||||
# 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)]
|
||||
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
|
||||
def help(self, line=None, column=None):
|
||||
"""
|
||||
|
||||
@@ -357,80 +357,7 @@ class Completion:
|
||||
def _complete_trailer_for_values(self, values):
|
||||
user_context = get_user_context(self._module_context, self._position)
|
||||
|
||||
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 += 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 []
|
||||
return complete_trailer(user_context, values)
|
||||
|
||||
def _get_importer_names(self, names, level=0, only_modules=True):
|
||||
names = [n.value for n in names]
|
||||
@@ -568,3 +495,81 @@ def _extract_string_while_in_string(leaf, position):
|
||||
leaves.insert(0, leaf)
|
||||
leaf = leaf.get_previous_leaf()
|
||||
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))
|
||||
return func(self, line, column, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def split_search_string(name):
|
||||
type, _, dotted_names = name.rpartition(' ')
|
||||
return type, dotted_names.split('.')
|
||||
|
||||
Reference in New Issue
Block a user