1313from dataclasses import dataclass
1414from itertools import chain
1515from tokenize import TokenInfo
16+ from .fancycompleter import safe_getattr
1617
1718TYPE_CHECKING = False
1819
@@ -71,7 +72,7 @@ def __init__(self, namespace: Mapping[str, Any] | None = None) -> None:
7172 self ._curr_sys_path : list [str ] = sys .path [:]
7273 self ._stdlib_path = os .path .dirname (importlib .__path__ [0 ])
7374
74- def get_completions (self , line : str ) -> tuple [list [str ], CompletionAction | None ] | None :
75+ def get_completions (self , line : str ) -> tuple [list [str ], list [ Any ], CompletionAction | None ] | None :
7576 """Return the next possible import completions for 'line'.
7677
7778 For attributes completion, if the module to complete from is not
@@ -86,26 +87,40 @@ def get_completions(self, line: str) -> tuple[list[str], CompletionAction | None
8687 except Exception :
8788 # Some unexpected error occurred, make it look like
8889 # no completions are available
89- return [], None
90+ return [], [], None
9091
91- def complete (self , from_name : str | None , name : str | None ) -> tuple [list [str ], CompletionAction | None ]:
92+ def complete (self , from_name : str | None , name : str | None ) -> tuple [list [str ], list [ Any ], CompletionAction | None ]:
9293 if from_name is None :
9394 # import x.y.z<tab>
9495 assert name is not None
9596 path , prefix = self .get_path_and_prefix (name )
9697 modules = self .find_modules (path , prefix )
97- return [self .format_completion (path , module ) for module in modules ], None
98+ names = [self .format_completion (path , module ) for module in modules ]
99+ # These are always modules, use dummy values to get the right color
100+ values = [sys ] * len (names )
101+ return names , values , None
98102
99103 if name is None :
100104 # from x.y.z<tab>
101105 path , prefix = self .get_path_and_prefix (from_name )
102106 modules = self .find_modules (path , prefix )
103- return [self .format_completion (path , module ) for module in modules ], None
107+ names = [self .format_completion (path , module ) for module in modules ]
108+ # These are always modules, use dummy values to get the right color
109+ values = [sys ] * len (names )
110+ return names , values , None
104111
105112 # from x.y import z<tab>
106113 submodules = self .find_modules (from_name , name )
107- attributes , action = self .find_attributes (from_name , name )
108- return sorted ({* submodules , * attributes }), action
114+ attr_names , attr_values , action = self .find_attributes (from_name , name )
115+ all_names = sorted ({* submodules , * attr_names })
116+ # Build values list matching the sorted order:
117+ # submodules use `sys` as a dummy value so they get the 'module' color,
118+ # attributes use their actual value.
119+ submodule_set = set (submodules )
120+ attr_map = dict (zip (attr_names , attr_values ))
121+ all_values = [attr_map .get (n ) if n not in submodule_set else sys
122+ for n in all_names ]
123+ return all_names , all_values , action
109124
110125 def find_modules (self , path : str , prefix : str ) -> list [str ]:
111126 """Find all modules under 'path' that start with 'prefix'."""
@@ -166,31 +181,43 @@ def _is_stdlib_module(self, module_info: pkgutil.ModuleInfo) -> bool:
166181 return (isinstance (module_info .module_finder , FileFinder )
167182 and module_info .module_finder .path == self ._stdlib_path )
168183
169- def find_attributes (self , path : str , prefix : str ) -> tuple [list [str ], CompletionAction | None ]:
184+ def find_attributes (self , path : str , prefix : str ) -> tuple [list [str ], list [ Any ], CompletionAction | None ]:
170185 """Find all attributes of module 'path' that start with 'prefix'."""
171- attributes , action = self ._find_attributes (path , prefix )
186+ attributes , values , action = self ._find_attributes (path , prefix )
172187 # Filter out invalid attribute names
173188 # (for example those containing dashes that cannot be imported with 'import')
174- return [attr for attr in attributes if attr .isidentifier ()], action
175-
176- def _find_attributes (self , path : str , prefix : str ) -> tuple [list [str ], CompletionAction | None ]:
189+ filtered_names = []
190+ filtered_values = []
191+ for attr , val in zip (attributes , values ):
192+ if attr .isidentifier ():
193+ filtered_names .append (attr )
194+ filtered_values .append (val )
195+ return filtered_names , filtered_values , action
196+
197+ def _find_attributes (self , path : str , prefix : str ) -> tuple [list [str ], list [Any ], CompletionAction | None ]:
177198 path = self ._resolve_relative_path (path ) # type: ignore[assignment]
178199 if path is None :
179- return [], None
200+ return [], [], None
180201
181202 imported_module = sys .modules .get (path )
182203 if not imported_module :
183204 if path in self ._failed_imports : # Do not propose to import again
184- return [], None
205+ return [], [], None
185206 imported_module = self ._maybe_import_module (path )
186207 if not imported_module :
187- return [], self ._get_import_completion_action (path )
208+ return [], [], self ._get_import_completion_action (path )
188209 try :
189210 module_attributes = dir (imported_module )
190211 except Exception :
191212 module_attributes = []
192- return [attr_name for attr_name in module_attributes
193- if self .is_suggestion_match (attr_name , prefix )], None
213+ names = []
214+ values = []
215+ for attr_name in module_attributes :
216+ if not self .is_suggestion_match (attr_name , prefix ):
217+ continue
218+ names .append (attr_name )
219+ values .append (safe_getattr (imported_module , attr_name ))
220+ return names , values , None
194221
195222 def is_suggestion_match (self , module_name : str , prefix : str ) -> bool :
196223 if prefix :
0 commit comments