1 """ 2 This module is here for string completions. This means mostly stuff where 3 strings are returned, like `foo = dict(bar=3); foo["ba` would complete to 4 `"bar"]`. 5 6 It however does the same for numbers. The difference between string completions 7 and other completions is mostly that this module doesn't return defined 8 names in a module, but pretty much an arbitrary string. 9 """ 10 import re 11 12 from jedi.inference.names import AbstractArbitraryName 13 from jedi.inference.helpers import infer_call_of_leaf 14 from jedi.api.classes import Completion 15 from jedi.parser_utils import cut_value_at_position 16 17 _sentinel = object() 18 19 20 class StringName(AbstractArbitraryName): 21 api_type = 'string' 22 is_value_name = False 23 24 25 def complete_dict(module_context, code_lines, leaf, position, string, fuzzy): 26 bracket_leaf = leaf 27 if bracket_leaf != '[': 28 bracket_leaf = leaf.get_previous_leaf() 29 30 cut_end_quote = '' 31 if string: 32 cut_end_quote = get_quote_ending(string, code_lines, position, invert_result=True) 33 34 if bracket_leaf == '[': 35 if string is None and leaf is not bracket_leaf: 36 string = cut_value_at_position(leaf, position) 37 38 context = module_context.create_context(bracket_leaf) 39 40 before_node = before_bracket_leaf = bracket_leaf.get_previous_leaf() 41 if before_node in (')', ']', '}'): 42 before_node = before_node.parent 43 if before_node.type in ('atom', 'trailer', 'name'): 44 values = infer_call_of_leaf(context, before_bracket_leaf) 45 return list(_completions_for_dicts( 46 module_context.inference_state, 47 values, 48 '' if string is None else string, 49 cut_end_quote, 50 fuzzy=fuzzy, 51 )) 52 return [] 53 54 55 def _completions_for_dicts(inference_state, dicts, literal_string, cut_end_quote, fuzzy): 56 for dict_key in sorted(_get_python_keys(dicts), key=lambda x: repr(x)): 57 dict_key_str = _create_repr_string(literal_string, dict_key) 58 if dict_key_str.startswith(literal_string): 59 name = StringName(inference_state, dict_key_str[:-len(cut_end_quote) or None]) 60 yield Completion( 61 inference_state, 62 name, 63 stack=None, 64 like_name_length=len(literal_string), 65 is_fuzzy=fuzzy 66 ) 67 68 69 def _create_repr_string(literal_string, dict_key): 70 if not isinstance(dict_key, (str, bytes)) or not literal_string: 71 return repr(dict_key) 72 73 r = repr(dict_key) 74 prefix, quote = _get_string_prefix_and_quote(literal_string) 75 if quote is None: 76 return r 77 if quote == r[0]: 78 return prefix + r 79 return prefix + quote + r[1:-1] + quote 80 81 82 def _get_python_keys(dicts): 83 for dct in dicts: 84 if dct.array_type == 'dict': 85 for key in dct.get_key_values(): 86 dict_key = key.get_safe_value(default=_sentinel) 87 if dict_key is not _sentinel: 88 yield dict_key 89 90 91 def _get_string_prefix_and_quote(string): 92 match = re.match(r'(\w*)("""|\'{3}|"|\')', string) 93 if match is None: 94 return None, None 95 return match.group(1), match.group(2) 96 97 98 def _matches_quote_at_position(code_lines, quote, position): 99 string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)] 100 return string == quote 101 102 103 def get_quote_ending(string, code_lines, position, invert_result=False): 104 _, quote = _get_string_prefix_and_quote(string) 105 if quote is None: 106 return '' 107 108 # Add a quote only if it's not already there. 109 if _matches_quote_at_position(code_lines, quote, position) != invert_result: 110 return '' 111 return quote 112