| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696 |
- import re
- from textwrap import dedent
- from inspect import Parameter
- from parso.python.token import PythonTokenTypes
- from parso.python import tree
- from parso.tree import search_ancestor, Leaf
- from parso import split_lines
- from jedi import debug
- from jedi import settings
- from jedi.api import classes
- from jedi.api import helpers
- from jedi.api import keywords
- from jedi.api.strings import complete_dict
- 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.docstring_utils import DocstringModule
- from jedi.inference.names import ParamNameWrapper, SubModuleName
- from jedi.inference.gradual.conversion import convert_values, convert_names
- from jedi.parser_utils import cut_value_at_position
- from jedi.plugins import plugin_manager
- class ParamNameWithEquals(ParamNameWrapper):
- def get_public_name(self):
- return self.string_name + '='
- def _get_signature_param_names(signatures, positional_count, used_kwargs):
- # Add named params
- for call_sig in signatures:
- for i, p in enumerate(call_sig.params):
- kind = p.kind
- if i < positional_count and kind == Parameter.POSITIONAL_OR_KEYWORD:
- continue
- if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) \
- and p.name not in used_kwargs:
- yield ParamNameWithEquals(p._name)
- def _must_be_kwarg(signatures, positional_count, used_kwargs):
- if used_kwargs:
- return True
- must_be_kwarg = True
- for signature in signatures:
- for i, p in enumerate(signature.params):
- kind = p.kind
- if kind is Parameter.VAR_POSITIONAL:
- # In case there were not already kwargs, the next param can
- # always be a normal argument.
- return False
- if i >= positional_count and kind in (Parameter.POSITIONAL_OR_KEYWORD,
- Parameter.POSITIONAL_ONLY):
- must_be_kwarg = False
- break
- if not must_be_kwarg:
- break
- return must_be_kwarg
- def filter_names(inference_state, completion_names, stack, like_name, fuzzy,
- imported_names, cached_name):
- comp_dct = set()
- if settings.case_insensitive_completion:
- like_name = like_name.lower()
- for name in completion_names:
- string = name.string_name
- if string in imported_names and string != like_name:
- continue
- if settings.case_insensitive_completion:
- string = string.lower()
- if helpers.match(string, like_name, fuzzy=fuzzy):
- new = classes.Completion(
- inference_state,
- name,
- stack,
- len(like_name),
- is_fuzzy=fuzzy,
- cached_name=cached_name,
- )
- k = (new.name, new.complete) # key
- if k not in comp_dct:
- comp_dct.add(k)
- tree_name = name.tree_name
- if tree_name is not None:
- definition = tree_name.get_definition()
- if definition is not None and definition.type == 'del_stmt':
- continue
- yield new
- def _remove_duplicates(completions, other_completions):
- names = {d.name for d in other_completions}
- return [c for c in completions if c.name not in names]
- def get_user_context(module_context, position):
- """
- Returns the scope in which the user resides. This includes flows.
- """
- leaf = module_context.tree_node.get_leaf_for_position(position, include_prefixes=True)
- return module_context.create_context(leaf)
- def get_flow_scope_node(module_node, position):
- node = module_node.get_leaf_for_position(position, include_prefixes=True)
- while not isinstance(node, (tree.Scope, tree.Flow)):
- node = node.parent
- 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,
- signatures_callback, fuzzy=False):
- self._inference_state = inference_state
- self._module_context = module_context
- self._module_node = module_context.tree_node
- self._code_lines = code_lines
- # The first step of completions is to get the name
- self._like_name = helpers.get_on_completion_name(self._module_node, code_lines, position)
- # The actual cursor position is not what we need to calculate
- # everything. We want the start of the name we're on.
- self._original_position = position
- self._signatures_callback = signatures_callback
- self._fuzzy = fuzzy
- # Return list of completions in this order:
- # - Beginning with what user is typing
- # - Public (alphabet)
- # - Private ("_xxx")
- # - Dunder ("__xxx")
- def complete(self):
- leaf = self._module_node.get_leaf_for_position(
- self._original_position,
- include_prefixes=True
- )
- string, start_leaf, quote = _extract_string_while_in_string(leaf, self._original_position)
- prefixed_completions = complete_dict(
- self._module_context,
- self._code_lines,
- start_leaf or leaf,
- self._original_position,
- None if string is None else quote + string,
- fuzzy=self._fuzzy,
- )
- if string is not None and not prefixed_completions:
- prefixed_completions = list(complete_file_name(
- self._inference_state, self._module_context, start_leaf, quote, string,
- self._like_name, self._signatures_callback,
- self._code_lines, self._original_position,
- self._fuzzy
- ))
- if string is not None:
- if not prefixed_completions and '\n' in string:
- # Complete only multi line strings
- prefixed_completions = self._complete_in_string(start_leaf, string)
- return prefixed_completions
- cached_name, completion_names = self._complete_python(leaf)
- imported_names = []
- if leaf.parent is not None and leaf.parent.type in ['import_as_names', 'dotted_as_names']:
- imported_names.extend(extract_imported_names(leaf.parent))
- completions = list(filter_names(self._inference_state, completion_names,
- self.stack, self._like_name,
- self._fuzzy, imported_names, cached_name=cached_name))
- return (
- # Removing duplicates mostly to remove False/True/None duplicates.
- _remove_duplicates(prefixed_completions, completions)
- + sorted(completions, key=lambda x: (not x.name.startswith(self._like_name),
- x.name.startswith('__'),
- x.name.startswith('_'),
- x.name.lower()))
- )
- def _complete_python(self, leaf):
- """
- Analyzes the current context of a completion and decides what to
- return.
- Technically this works by generating a parser stack and analysing the
- current stack for possible grammar nodes.
- Possible enhancements:
- - global/nonlocal search global
- - yield from / raise from <- could be only exceptions/generators
- - In args: */**: no completion
- - In params (also lambda): no completion before =
- """
- grammar = self._inference_state.grammar
- self.stack = stack = None
- self._position = (
- self._original_position[0],
- self._original_position[1] - len(self._like_name)
- )
- cached_name = None
- try:
- self.stack = stack = helpers.get_stack_at_position(
- grammar, self._code_lines, leaf, self._position
- )
- except helpers.OnErrorLeaf as e:
- value = e.error_leaf.value
- if value == '.':
- # After ErrorLeaf's that are dots, we will not do any
- # completions since this probably just confuses the user.
- return cached_name, []
- # If we don't have a value, just use global completion.
- return cached_name, self._complete_global_scope()
- allowed_transitions = \
- list(stack._allowed_transition_names_and_token_types())
- if 'if' in allowed_transitions:
- leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
- previous_leaf = leaf.get_previous_leaf()
- indent = self._position[1]
- if not (leaf.start_pos <= self._position <= leaf.end_pos):
- indent = leaf.start_pos[1]
- if previous_leaf is not None:
- stmt = previous_leaf
- while True:
- stmt = search_ancestor(
- stmt, 'if_stmt', 'for_stmt', 'while_stmt', 'try_stmt',
- 'error_node',
- )
- if stmt is None:
- break
- type_ = stmt.type
- if type_ == 'error_node':
- first = stmt.children[0]
- if isinstance(first, Leaf):
- type_ = first.value + '_stmt'
- # Compare indents
- if stmt.start_pos[1] == indent:
- if type_ == 'if_stmt':
- allowed_transitions += ['elif', 'else']
- elif type_ == 'try_stmt':
- allowed_transitions += ['except', 'finally', 'else']
- elif type_ == 'for_stmt':
- allowed_transitions.append('else')
- completion_names = []
- kwargs_only = False
- if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
- PythonTokenTypes.INDENT)):
- # This means that we actually have to do type inference.
- nonterminals = [stack_node.nonterminal for stack_node in stack]
- nodes = _gather_nodes(stack)
- 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 cached_name, list(self._complete_inherited(is_function=True))
- elif "import_stmt" in nonterminals:
- level, names = parse_dotted_names(nodes, "import_from" in nonterminals)
- only_modules = not ("import_from" in nonterminals and 'import' in nodes)
- completion_names += self._get_importer_names(
- names,
- level,
- only_modules=only_modules,
- )
- elif nonterminals[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.':
- dot = self._module_node.get_leaf_for_position(self._position)
- if dot.type == "endmarker":
- # This is a bit of a weird edge case, maybe we can somehow
- # generalize this.
- dot = leaf.get_previous_leaf()
- cached_name, n = self._complete_trailer(dot.get_previous_leaf())
- completion_names += n
- elif self._is_parameter_completion():
- completion_names += self._complete_params(leaf)
- else:
- # 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)
- if signatures:
- call_details = signatures[0]._call_details
- used_kwargs = list(call_details.iter_used_keyword_arguments())
- positional_count = call_details.count_positional_arguments()
- completion_names += _get_signature_param_names(
- signatures,
- positional_count,
- used_kwargs,
- )
- kwargs_only = _must_be_kwarg(signatures, positional_count, used_kwargs)
- if not kwargs_only:
- completion_names += self._complete_global_scope()
- completion_names += self._complete_inherited(is_function=False)
- if not kwargs_only:
- current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
- completion_names += self._complete_keywords(
- allowed_transitions,
- only_values=not (not current_line or current_line[-1] in ' \t.;'
- and current_line[-3:] != '...')
- )
- return cached_name, completion_names
- 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 is not None:
- 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, only_values):
- for k in allowed_transitions:
- if isinstance(k, str) and k.isalpha():
- if not only_values or k in ('True', 'False', 'None'):
- yield keywords.KeywordName(self._inference_state, k)
- 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)
- filters = get_global_filters(
- context,
- self._position,
- flow_scope_node
- )
- completion_names = []
- for filter in filters:
- completion_names += filter.values()
- return completion_names
- def _complete_trailer(self, previous_leaf):
- inferred_context = self._module_context.create_context(previous_leaf)
- values = infer_call_of_leaf(inferred_context, previous_leaf)
- debug.dbg('trailer completion values: %s', values, color='MAGENTA')
- # The cached name simply exists to make speed optimizations for certain
- # modules.
- cached_name = None
- if len(values) == 1:
- v, = values
- if v.is_module():
- if len(v.string_names) == 1:
- module_name = v.string_names[0]
- if module_name in ('numpy', 'tensorflow', 'matplotlib', 'pandas'):
- cached_name = module_name
- return cached_name, self._complete_trailer_for_values(values)
- def _complete_trailer_for_values(self, values):
- user_context = get_user_context(self._module_context, self._position)
- return complete_trailer(user_context, values)
- 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 _complete_inherited(self, is_function=True):
- """
- Autocomplete inherited methods when overriding in child class.
- """
- leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
- cls = tree.search_ancestor(leaf, 'classdef')
- if cls is None:
- return
- # Complete the methods that are defined in the super classes.
- class_value = self._module_context.create_value(cls)
- if cls.start_pos[1] >= leaf.start_pos[1]:
- return
- filters = class_value.get_filters(is_instance=True)
- # The first dict is the dictionary of class itself.
- next(filters)
- for filter in filters:
- for name in filter.values():
- # TODO we should probably check here for properties
- if (name.api_type == 'function') == is_function:
- yield name
- def _complete_in_string(self, start_leaf, string):
- """
- To make it possible for people to have completions in doctests or
- generally in "Python" code in docstrings, we use the following
- heuristic:
- - Having an indented block of code
- - Having some doctest code that starts with `>>>`
- - Having backticks that doesn't have whitespace inside it
- """
- def iter_relevant_lines(lines):
- include_next_line = False
- for l in code_lines:
- if include_next_line or l.startswith('>>>') or l.startswith(' '):
- yield re.sub(r'^( *>>> ?| +)', '', l)
- else:
- yield None
- include_next_line = bool(re.match(' *>>>', l))
- string = dedent(string)
- code_lines = split_lines(string, keepends=True)
- relevant_code_lines = list(iter_relevant_lines(code_lines))
- if relevant_code_lines[-1] is not None:
- # Some code lines might be None, therefore get rid of that.
- relevant_code_lines = ['\n' if c is None else c for c in relevant_code_lines]
- return self._complete_code_lines(relevant_code_lines)
- match = re.search(r'`([^`\s]+)', code_lines[-1])
- if match:
- return self._complete_code_lines([match.group(1)])
- return []
- def _complete_code_lines(self, code_lines):
- module_node = self._inference_state.grammar.parse(''.join(code_lines))
- module_value = DocstringModule(
- in_module_context=self._module_context,
- inference_state=self._inference_state,
- module_node=module_node,
- code_lines=code_lines,
- )
- return Completion(
- self._inference_state,
- module_value.as_context(),
- code_lines=code_lines,
- position=module_node.end_pos,
- signatures_callback=lambda *args, **kwargs: [],
- fuzzy=self._fuzzy
- ).complete()
- def _gather_nodes(stack):
- nodes = []
- for stack_node in stack:
- if stack_node.dfa.from_rule == 'small_stmt':
- nodes = []
- else:
- nodes += stack_node.nodes
- return nodes
- _string_start = re.compile(r'^\w*(\'{3}|"{3}|\'|")')
- def _extract_string_while_in_string(leaf, position):
- def return_part_of_leaf(leaf):
- kwargs = {}
- if leaf.line == position[0]:
- kwargs['endpos'] = position[1] - leaf.column
- match = _string_start.match(leaf.value, **kwargs)
- if not match:
- return None, None, None
- start = match.group(0)
- if leaf.line == position[0] and position[1] < leaf.column + match.end():
- return None, None, None
- return cut_value_at_position(leaf, position)[match.end():], leaf, start
- if position < leaf.start_pos:
- return None, None, None
- if leaf.type == 'string':
- return return_part_of_leaf(leaf)
- leaves = []
- while leaf is not None:
- if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value):
- if len(leaf.value) > 1:
- return return_part_of_leaf(leaf)
- prefix_leaf = None
- if not leaf.prefix:
- prefix_leaf = leaf.get_previous_leaf()
- if prefix_leaf is None or prefix_leaf.type != 'name' \
- or not all(c in 'rubf' for c in prefix_leaf.value.lower()):
- prefix_leaf = None
- return (
- ''.join(cut_value_at_position(l, position) for l in leaves),
- prefix_leaf or leaf,
- ('' if prefix_leaf is None else prefix_leaf.value)
- + cut_value_at_position(leaf, position),
- )
- if leaf.line != position[0]:
- # Multi line strings are always simple error leaves and contain the
- # whole string, single line error leaves are atherefore important
- # now and since the line is different, it's not really a single
- # line string anymore.
- break
- 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('__getattr__')
- or instance.get_function_slot_names('__getattribute__'))
- functions = ValueSet.from_sets(
- name.infer()
- for name in names
- )
- for func in functions:
- tree_node = func.tree_node
- if tree_node is None or tree_node.type != 'funcdef':
- continue
- 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 []
- def search_in_module(inference_state, module_context, names, wanted_names,
- wanted_type, complete=False, fuzzy=False,
- ignore_imports=False, convert=False):
- for s in wanted_names[:-1]:
- new_names = []
- for n in names:
- if s == n.string_name:
- if n.tree_name is not None and n.api_type in ('module', 'namespace') \
- and ignore_imports:
- continue
- new_names += complete_trailer(
- module_context,
- n.infer()
- )
- debug.dbg('dot lookup on search %s from %s', new_names, names[:10])
- names = new_names
- last_name = wanted_names[-1].lower()
- for n in names:
- string = n.string_name.lower()
- if complete and helpers.match(string, last_name, fuzzy=fuzzy) \
- or not complete and string == last_name:
- if isinstance(n, SubModuleName):
- names = [v.name for v in n.infer()]
- else:
- names = [n]
- if convert:
- names = convert_names(names)
- for n2 in names:
- if complete:
- def_ = classes.Completion(
- inference_state, n2,
- stack=None,
- like_name_length=len(last_name),
- is_fuzzy=fuzzy,
- )
- else:
- def_ = classes.Name(inference_state, n2)
- if not wanted_type or wanted_type == def_.type:
- yield def_
- def extract_imported_names(node):
- imported_names = []
- if node.type in ['import_as_names', 'dotted_as_names', 'import_as_name']:
- for index, child in enumerate(node.children):
- if child.type == 'name':
- if (index > 0 and node.children[index - 1].type == "keyword"
- and node.children[index - 1].value == "as"):
- continue
- imported_names.append(child.value)
- elif child.type == 'import_as_name':
- imported_names.extend(extract_imported_names(child))
- return imported_names
|