]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | import os |
2 | ||
3 | from jedi.api import classes | |
4 | from jedi.api.strings import StringName, get_quote_ending | |
5 | from jedi.api.helpers import match | |
6 | from jedi.inference.helpers import get_str_or_none | |
7 | ||
8 | ||
9 | class PathName(StringName): | |
10 | api_type = 'path' | |
11 | ||
12 | ||
13 | def complete_file_name(inference_state, module_context, start_leaf, quote, string, | |
14 | like_name, signatures_callback, code_lines, position, fuzzy): | |
15 | # First we want to find out what can actually be changed as a name. | |
16 | like_name_length = len(os.path.basename(string)) | |
17 | ||
18 | addition = _get_string_additions(module_context, start_leaf) | |
19 | if string.startswith('~'): | |
20 | string = os.path.expanduser(string) | |
21 | if addition is None: | |
22 | return | |
23 | string = addition + string | |
24 | ||
25 | # Here we use basename again, because if strings are added like | |
26 | # `'foo' + 'bar`, it should complete to `foobar/`. | |
27 | must_start_with = os.path.basename(string) | |
28 | string = os.path.dirname(string) | |
29 | ||
30 | sigs = signatures_callback(*position) | |
31 | is_in_os_path_join = sigs and all(s.full_name == 'os.path.join' for s in sigs) | |
32 | if is_in_os_path_join: | |
33 | to_be_added = _add_os_path_join(module_context, start_leaf, sigs[0].bracket_start) | |
34 | if to_be_added is None: | |
35 | is_in_os_path_join = False | |
36 | else: | |
37 | string = to_be_added + string | |
38 | base_path = os.path.join(inference_state.project.path, string) | |
39 | try: | |
40 | listed = sorted(os.scandir(base_path), key=lambda e: e.name) | |
41 | # OSError: [Errno 36] File name too long: '...' | |
42 | except (FileNotFoundError, OSError): | |
43 | return | |
44 | quote_ending = get_quote_ending(quote, code_lines, position) | |
45 | for entry in listed: | |
46 | name = entry.name | |
47 | if match(name, must_start_with, fuzzy=fuzzy): | |
48 | if is_in_os_path_join or not entry.is_dir(): | |
49 | name += quote_ending | |
50 | else: | |
51 | name += os.path.sep | |
52 | ||
53 | yield classes.Completion( | |
54 | inference_state, | |
55 | PathName(inference_state, name[len(must_start_with) - like_name_length:]), | |
56 | stack=None, | |
57 | like_name_length=like_name_length, | |
58 | is_fuzzy=fuzzy, | |
59 | ) | |
60 | ||
61 | ||
62 | def _get_string_additions(module_context, start_leaf): | |
63 | def iterate_nodes(): | |
64 | node = addition.parent | |
65 | was_addition = True | |
66 | for child_node in reversed(node.children[:node.children.index(addition)]): | |
67 | if was_addition: | |
68 | was_addition = False | |
69 | yield child_node | |
70 | continue | |
71 | ||
72 | if child_node != '+': | |
73 | break | |
74 | was_addition = True | |
75 | ||
76 | addition = start_leaf.get_previous_leaf() | |
77 | if addition != '+': | |
78 | return '' | |
79 | context = module_context.create_context(start_leaf) | |
80 | return _add_strings(context, reversed(list(iterate_nodes()))) | |
81 | ||
82 | ||
83 | def _add_strings(context, nodes, add_slash=False): | |
84 | string = '' | |
85 | first = True | |
86 | for child_node in nodes: | |
87 | values = context.infer_node(child_node) | |
88 | if len(values) != 1: | |
89 | return None | |
90 | c, = values | |
91 | s = get_str_or_none(c) | |
92 | if s is None: | |
93 | return None | |
94 | if not first and add_slash: | |
95 | string += os.path.sep | |
96 | string += s | |
97 | first = False | |
98 | return string | |
99 | ||
100 | ||
101 | def _add_os_path_join(module_context, start_leaf, bracket_start): | |
102 | def check(maybe_bracket, nodes): | |
103 | if maybe_bracket.start_pos != bracket_start: | |
104 | return None | |
105 | ||
106 | if not nodes: | |
107 | return '' | |
108 | context = module_context.create_context(nodes[0]) | |
109 | return _add_strings(context, nodes, add_slash=True) or '' | |
110 | ||
111 | if start_leaf.type == 'error_leaf': | |
112 | # Unfinished string literal, like `join('` | |
113 | value_node = start_leaf.parent | |
114 | index = value_node.children.index(start_leaf) | |
115 | if index > 0: | |
116 | error_node = value_node.children[index - 1] | |
117 | if error_node.type == 'error_node' and len(error_node.children) >= 2: | |
118 | index = -2 | |
119 | if error_node.children[-1].type == 'arglist': | |
120 | arglist_nodes = error_node.children[-1].children | |
121 | index -= 1 | |
122 | else: | |
123 | arglist_nodes = [] | |
124 | ||
125 | return check(error_node.children[index + 1], arglist_nodes[::2]) | |
126 | return None | |
127 | ||
128 | # Maybe an arglist or some weird error case. Therefore checked below. | |
129 | searched_node_child = start_leaf | |
130 | while searched_node_child.parent is not None \ | |
131 | and searched_node_child.parent.type not in ('arglist', 'trailer', 'error_node'): | |
132 | searched_node_child = searched_node_child.parent | |
133 | ||
134 | if searched_node_child.get_first_leaf() is not start_leaf: | |
135 | return None | |
136 | searched_node = searched_node_child.parent | |
137 | if searched_node is None: | |
138 | return None | |
139 | ||
140 | index = searched_node.children.index(searched_node_child) | |
141 | arglist_nodes = searched_node.children[:index] | |
142 | if searched_node.type == 'arglist': | |
143 | trailer = searched_node.parent | |
144 | if trailer.type == 'error_node': | |
145 | trailer_index = trailer.children.index(searched_node) | |
146 | assert trailer_index >= 2 | |
147 | assert trailer.children[trailer_index - 1] == '(' | |
148 | return check(trailer.children[trailer_index - 1], arglist_nodes[::2]) | |
149 | elif trailer.type == 'trailer': | |
150 | return check(trailer.children[0], arglist_nodes[::2]) | |
151 | elif searched_node.type == 'trailer': | |
152 | return check(searched_node.children[0], []) | |
153 | elif searched_node.type == 'error_node': | |
154 | # Stuff like `join(""` | |
155 | return check(arglist_nodes[-1], []) |