110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
|
"""
|
||
|
This module is here for string completions. This means mostly stuff where
|
||
|
strings are returned, like `foo = dict(bar=3); foo["ba` would complete to
|
||
|
`"bar"]`.
|
||
|
|
||
|
It however does the same for numbers. The difference between string completions
|
||
|
and other completions is mostly that this module doesn't return defined
|
||
|
names in a module, but pretty much an arbitrary string.
|
||
|
"""
|
||
|
import re
|
||
|
|
||
|
from jedi._compatibility import unicode
|
||
|
from jedi.inference.names import AbstractArbitraryName
|
||
|
from jedi.inference.helpers import infer_call_of_leaf
|
||
|
from jedi.api.classes import Completion
|
||
|
from jedi.parser_utils import cut_value_at_position
|
||
|
|
||
|
_sentinel = object()
|
||
|
|
||
|
|
||
|
class StringName(AbstractArbitraryName):
|
||
|
api_type = u'string'
|
||
|
is_value_name = False
|
||
|
|
||
|
|
||
|
def complete_dict(module_context, code_lines, leaf, position, string, fuzzy):
|
||
|
bracket_leaf = leaf
|
||
|
if bracket_leaf != '[':
|
||
|
bracket_leaf = leaf.get_previous_leaf()
|
||
|
|
||
|
cut_end_quote = ''
|
||
|
if string:
|
||
|
cut_end_quote = get_quote_ending(string, code_lines, position, invert_result=True)
|
||
|
|
||
|
if bracket_leaf == '[':
|
||
|
if string is None and leaf is not bracket_leaf:
|
||
|
string = cut_value_at_position(leaf, position)
|
||
|
|
||
|
context = module_context.create_context(bracket_leaf)
|
||
|
before_bracket_leaf = bracket_leaf.get_previous_leaf()
|
||
|
if before_bracket_leaf.type in ('atom', 'trailer', 'name'):
|
||
|
values = infer_call_of_leaf(context, before_bracket_leaf)
|
||
|
return list(_completions_for_dicts(
|
||
|
module_context.inference_state,
|
||
|
values,
|
||
|
'' if string is None else string,
|
||
|
cut_end_quote,
|
||
|
fuzzy=fuzzy,
|
||
|
))
|
||
|
return []
|
||
|
|
||
|
|
||
|
def _completions_for_dicts(inference_state, dicts, literal_string, cut_end_quote, fuzzy):
|
||
|
for dict_key in sorted(_get_python_keys(dicts), key=lambda x: repr(x)):
|
||
|
dict_key_str = _create_repr_string(literal_string, dict_key)
|
||
|
if dict_key_str.startswith(literal_string):
|
||
|
name = StringName(inference_state, dict_key_str[:-len(cut_end_quote) or None])
|
||
|
yield Completion(
|
||
|
inference_state,
|
||
|
name,
|
||
|
stack=None,
|
||
|
like_name_length=len(literal_string),
|
||
|
is_fuzzy=fuzzy
|
||
|
)
|
||
|
|
||
|
|
||
|
def _create_repr_string(literal_string, dict_key):
|
||
|
if not isinstance(dict_key, (unicode, bytes)) or not literal_string:
|
||
|
return repr(dict_key)
|
||
|
|
||
|
r = repr(dict_key)
|
||
|
prefix, quote = _get_string_prefix_and_quote(literal_string)
|
||
|
if quote is None:
|
||
|
return r
|
||
|
if quote == r[0]:
|
||
|
return prefix + r
|
||
|
return prefix + quote + r[1:-1] + quote
|
||
|
|
||
|
|
||
|
def _get_python_keys(dicts):
|
||
|
for dct in dicts:
|
||
|
if dct.array_type == 'dict':
|
||
|
for key in dct.get_key_values():
|
||
|
dict_key = key.get_safe_value(default=_sentinel)
|
||
|
if dict_key is not _sentinel:
|
||
|
yield dict_key
|
||
|
|
||
|
|
||
|
def _get_string_prefix_and_quote(string):
|
||
|
match = re.match(r'(\w*)("""|\'{3}|"|\')', string)
|
||
|
if match is None:
|
||
|
return None, None
|
||
|
return match.group(1), match.group(2)
|
||
|
|
||
|
|
||
|
def _matches_quote_at_position(code_lines, quote, position):
|
||
|
string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
|
||
|
return string == quote
|
||
|
|
||
|
|
||
|
def get_quote_ending(string, code_lines, position, invert_result=False):
|
||
|
_, quote = _get_string_prefix_and_quote(string)
|
||
|
if quote is None:
|
||
|
return ''
|
||
|
|
||
|
# Add a quote only if it's not already there.
|
||
|
if _matches_quote_at_position(code_lines, quote, position) != invert_result:
|
||
|
return ''
|
||
|
return quote
|