]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
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 |