|
13 | 13 | from dataclasses import dataclass |
14 | 14 | from itertools import chain |
15 | 15 | from tokenize import TokenInfo |
| 16 | +from .fancycompleter import safe_getattr |
16 | 17 |
|
17 | 18 | TYPE_CHECKING = False |
18 | 19 |
|
@@ -71,41 +72,69 @@ def __init__(self, namespace: Mapping[str, Any] | None = None) -> None: |
71 | 72 | self._curr_sys_path: list[str] = sys.path[:] |
72 | 73 | self._stdlib_path = os.path.dirname(importlib.__path__[0]) |
73 | 74 |
|
74 | | - def get_completions(self, line: str) -> tuple[list[str], CompletionAction | None] | None: |
| 75 | + def get_completions( |
| 76 | + self, line: str, *, include_values: bool = True |
| 77 | + ) -> tuple[list[str], list[Any], CompletionAction | None] | None: |
75 | 78 | """Return the next possible import completions for 'line'. |
76 | 79 |
|
77 | 80 | For attributes completion, if the module to complete from is not |
78 | 81 | imported, also return an action (prompt + callback to run if the |
79 | 82 | user press TAB again) to import the module. |
| 83 | +
|
| 84 | + If *include_values* is false, the returned values list is empty and |
| 85 | + attribute values are not resolved. |
80 | 86 | """ |
81 | 87 | result = ImportParser(line).parse() |
82 | 88 | if not result: |
83 | 89 | return None |
84 | 90 | try: |
85 | | - return self.complete(*result) |
| 91 | + return self.complete(*result, include_values=include_values) |
86 | 92 | except Exception: |
87 | 93 | # Some unexpected error occurred, make it look like |
88 | 94 | # no completions are available |
89 | | - return [], None |
90 | | - |
91 | | - def complete(self, from_name: str | None, name: str | None) -> tuple[list[str], CompletionAction | None]: |
| 95 | + return [], [], None |
| 96 | + |
| 97 | + def complete( |
| 98 | + self, |
| 99 | + from_name: str | None, |
| 100 | + name: str | None, |
| 101 | + *, |
| 102 | + include_values: bool = True, |
| 103 | + ) -> tuple[list[str], list[Any], CompletionAction | None]: |
92 | 104 | if from_name is None: |
93 | 105 | # import x.y.z<tab> |
94 | 106 | assert name is not None |
95 | 107 | path, prefix = self.get_path_and_prefix(name) |
96 | 108 | modules = self.find_modules(path, prefix) |
97 | | - return [self.format_completion(path, module) for module in modules], None |
| 109 | + names = [self.format_completion(path, module) for module in modules] |
| 110 | + # These are always modules, use dummy values to get the right color |
| 111 | + values = [sys] * len(names) if include_values else [] |
| 112 | + return names, values, None |
98 | 113 |
|
99 | 114 | if name is None: |
100 | 115 | # from x.y.z<tab> |
101 | 116 | path, prefix = self.get_path_and_prefix(from_name) |
102 | 117 | modules = self.find_modules(path, prefix) |
103 | | - return [self.format_completion(path, module) for module in modules], None |
| 118 | + names = [self.format_completion(path, module) for module in modules] |
| 119 | + # These are always modules, use dummy values to get the right color |
| 120 | + values = [sys] * len(names) if include_values else [] |
| 121 | + return names, values, None |
104 | 122 |
|
105 | 123 | # from x.y import z<tab> |
106 | 124 | submodules = self.find_modules(from_name, name) |
107 | | - attributes, action = self.find_attributes(from_name, name) |
108 | | - return sorted({*submodules, *attributes}), action |
| 125 | + attr_names, attr_module, action = self._find_attributes(from_name, name) |
| 126 | + all_names = sorted({*submodules, *attr_names}) |
| 127 | + if not include_values: |
| 128 | + return all_names, [], action |
| 129 | + |
| 130 | + # Build values list matching the sorted order: |
| 131 | + # submodules use `sys` as a dummy value so they get the 'module' color, |
| 132 | + # attributes use their actual value. |
| 133 | + attr_map = {} |
| 134 | + if attr_module is not None: |
| 135 | + attr_map = {n: safe_getattr(attr_module, n) for n in attr_names} |
| 136 | + all_values = [attr_map[n] if n in attr_map else sys for n in all_names] |
| 137 | + return all_names, all_values, action |
109 | 138 |
|
110 | 139 | def find_modules(self, path: str, prefix: str) -> list[str]: |
111 | 140 | """Find all modules under 'path' that start with 'prefix'.""" |
@@ -166,31 +195,43 @@ def _is_stdlib_module(self, module_info: pkgutil.ModuleInfo) -> bool: |
166 | 195 | return (isinstance(module_info.module_finder, FileFinder) |
167 | 196 | and module_info.module_finder.path == self._stdlib_path) |
168 | 197 |
|
169 | | - def find_attributes(self, path: str, prefix: str) -> tuple[list[str], CompletionAction | None]: |
| 198 | + def find_attributes( |
| 199 | + self, path: str, prefix: str |
| 200 | + ) -> tuple[list[str], list[Any], CompletionAction | None]: |
170 | 201 | """Find all attributes of module 'path' that start with 'prefix'.""" |
171 | | - attributes, action = self._find_attributes(path, prefix) |
172 | | - # Filter out invalid attribute names |
173 | | - # (for example those containing dashes that cannot be imported with 'import') |
174 | | - return [attr for attr in attributes if attr.isidentifier()], action |
| 202 | + attributes, module, action = self._find_attributes(path, prefix) |
| 203 | + if module is not None: |
| 204 | + values = [safe_getattr(module, attr) for attr in attributes] |
| 205 | + else: |
| 206 | + values = [] |
| 207 | + return attributes, values, action |
175 | 208 |
|
176 | | - def _find_attributes(self, path: str, prefix: str) -> tuple[list[str], CompletionAction | None]: |
| 209 | + def _find_attributes( |
| 210 | + self, path: str, prefix: str |
| 211 | + ) -> tuple[list[str], ModuleType | None, CompletionAction | None]: |
177 | 212 | path = self._resolve_relative_path(path) # type: ignore[assignment] |
178 | 213 | if path is None: |
179 | | - return [], None |
| 214 | + return [], None, None |
180 | 215 |
|
181 | 216 | imported_module = sys.modules.get(path) |
182 | 217 | if not imported_module: |
183 | 218 | if path in self._failed_imports: # Do not propose to import again |
184 | | - return [], None |
| 219 | + return [], None, None |
185 | 220 | imported_module = self._maybe_import_module(path) |
186 | 221 | if not imported_module: |
187 | | - return [], self._get_import_completion_action(path) |
| 222 | + return [], None, self._get_import_completion_action(path) |
188 | 223 | try: |
189 | 224 | module_attributes = dir(imported_module) |
190 | 225 | except Exception: |
191 | 226 | module_attributes = [] |
192 | | - return [attr_name for attr_name in module_attributes |
193 | | - if self.is_suggestion_match(attr_name, prefix)], None |
| 227 | + # Filter out invalid attribute names, such as dashes that cannot be |
| 228 | + # imported with 'import'. |
| 229 | + names = [ |
| 230 | + attr_name for attr_name in module_attributes |
| 231 | + if (self.is_suggestion_match(attr_name, prefix) |
| 232 | + and attr_name.isidentifier()) |
| 233 | + ] |
| 234 | + return names, imported_module, None |
194 | 235 |
|
195 | 236 | def is_suggestion_match(self, module_name: str, prefix: str) -> bool: |
196 | 237 | if prefix: |
|
0 commit comments