66
77from ptpython .utils import get_jedi_script_from_document
88
9+ import keyword
10+ import ast
911import re
12+ import six
1013
1114__all__ = (
1215 'PythonCompleter' ,
@@ -17,11 +20,14 @@ class PythonCompleter(Completer):
1720 """
1821 Completer for Python code.
1922 """
20- def __init__ (self , get_globals , get_locals ):
23+ def __init__ (self , get_globals , get_locals , get_enable_dictionary_completion ):
2124 super (PythonCompleter , self ).__init__ ()
2225
2326 self .get_globals = get_globals
2427 self .get_locals = get_locals
28+ self .get_enable_dictionary_completion = get_enable_dictionary_completion
29+
30+ self .dictionary_completer = DictionaryCompleter (get_globals , get_locals )
2531
2632 self ._path_completer_cache = None
2733 self ._path_completer_grammar_cache = None
@@ -108,7 +114,16 @@ def get_completions(self, document, complete_event):
108114 """
109115 Get Python completions.
110116 """
111- # Do Path completions
117+ # Do dictionary key completions.
118+ if self .get_enable_dictionary_completion ():
119+ has_dict_completions = False
120+ for c in self .dictionary_completer .get_completions (document , complete_event ):
121+ has_dict_completions = True
122+ yield c
123+ if has_dict_completions :
124+ return
125+
126+ # Do Path completions (if there were no dictionary completions).
112127 if complete_event .completion_requested or self ._complete_path_while_typing (document ):
113128 for c in self ._path_completer .get_completions (document , complete_event ):
114129 yield c
@@ -162,5 +177,107 @@ def get_completions(self, document, complete_event):
162177 pass
163178 else :
164179 for c in completions :
165- yield Completion (c .name_with_symbols , len (c .complete ) - len (c .name_with_symbols ),
166- display = c .name_with_symbols )
180+ yield Completion (
181+ c .name_with_symbols , len (c .complete ) - len (c .name_with_symbols ),
182+ display = c .name_with_symbols ,
183+ style = _get_style_for_name (c .name_with_symbols ))
184+
185+
186+ class DictionaryCompleter (Completer ):
187+ """
188+ Experimental completer for Python dictionary keys.
189+
190+ Warning: This does an `eval` on the Python object before the open square
191+ bracket, which is potentially dangerous. It doesn't match on
192+ function calls, so it only triggers attribute access.
193+ """
194+ def __init__ (self , get_globals , get_locals ):
195+ super (DictionaryCompleter , self ).__init__ ()
196+
197+ self .get_globals = get_globals
198+ self .get_locals = get_locals
199+
200+ self .pattern = re .compile (
201+ r'''
202+ # Any expression safe enough to eval while typing.
203+ # No operators, except dot, and only other dict lookups.
204+ # Technically, this can be unsafe of course, if bad code runs
205+ # in `__getattr__` or ``__getitem__``.
206+ (
207+ # Variable name
208+ [a-zA-Z0-9_]+
209+
210+ \s*
211+
212+ (?:
213+ # Attribute access.
214+ \s* \. \s* [a-zA-Z0-9_]+ \s*
215+
216+ |
217+
218+ # Item lookup.
219+ # (We match the square brackets. We don't care about
220+ # matching quotes here in the regex. Nested square
221+ # brackets are not supported.)
222+ \s* \[ [a-zA-Z0-9_'"\s]+ \] \s*
223+ )*
224+ )
225+
226+ # Dict loopup to complete (square bracket open + start of
227+ # string).
228+ \[
229+ \s* ([a-zA-Z0-9_'"]*)$
230+ ''' ,
231+ re .VERBOSE
232+ )
233+
234+ def get_completions (self , document , complete_event ):
235+ match = self .pattern .search (document .text_before_cursor )
236+ if match is not None :
237+ object_var , key = match .groups ()
238+ object_var = object_var .strip ()
239+
240+ # Do lookup of `object_var` in the context.
241+ try :
242+ result = eval (object_var , self .get_globals (), self .get_locals ())
243+ except BaseException as e :
244+ return # Many exception, like NameError can be thrown here.
245+
246+ # If this object is a dictionary, complete the keys.
247+ if isinstance (result , dict ):
248+ # Try to evaluate the key.
249+ key_obj = key
250+ for k in [key , key + '"' , key + "'" ]:
251+ try :
252+ key_obj = ast .literal_eval (k )
253+ except (SyntaxError , ValueError ):
254+ continue
255+ else :
256+ break
257+
258+ for k in result :
259+ if six .text_type (k ).startswith (key_obj ):
260+ yield Completion (
261+ six .text_type (repr (k )),
262+ - len (key ),
263+ display = six .text_type (repr (k ))
264+ )
265+
266+ try :
267+ import builtins
268+ _builtin_names = dir (builtins )
269+ except ImportError : # Python 2.
270+ _builtin_names = []
271+
272+
273+ def _get_style_for_name (name ):
274+ """
275+ Return completion style to use for this name.
276+ """
277+ if name in _builtin_names :
278+ return 'class:completion.builtin'
279+
280+ if keyword .iskeyword (name ):
281+ return 'class:completion.keyword'
282+
283+ return ''
0 commit comments