Skip to content

Commit e2e9b8f

Browse files
committed
Merged in mjzffr/bpython (pull request bpython#41)
Autocomplete for dictionary keys (Fixes bpython#226)
2 parents a31ee9e + 34eb9ac commit e2e9b8f

File tree

2 files changed

+62
-9
lines changed

2 files changed

+62
-9
lines changed

bpython/autocomplete.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,49 @@ def __init__(self, namespace = None, config = None):
5454
else:
5555
self.autocomplete_mode = SUBSTRING
5656

57+
def complete(self, text, state):
58+
"""Return the next possible completion for 'text'.
59+
60+
This is called successively with state == 0, 1, 2, ... until it
61+
returns None. The completion should begin with 'text'.
62+
63+
"""
64+
if self.use_main_ns:
65+
self.namespace = __main__.__dict__
66+
67+
dictpattern = re.compile('[^\[\]]+\[$')
68+
def complete_dict(text):
69+
lastbracket_index = text.rindex('[')
70+
dexpr = text[:lastbracket_index].lstrip()
71+
obj = eval(dexpr, self.locals)
72+
if obj and isinstance(obj, type({})) and obj.keys():
73+
self.matches = [dexpr + "[{!r}]".format(k) for k in obj.keys()]
74+
else:
75+
# empty dictionary
76+
self.matches = []
77+
78+
if state == 0:
79+
if "." in text:
80+
if dictpattern.match(text):
81+
complete_dict(text)
82+
else:
83+
# Examples: 'foo.b' or 'foo[bar.'
84+
for i in range(1, len(text) + 1):
85+
if text[-i] == '[':
86+
i -= 1
87+
break
88+
methodtext = text[-i:]
89+
self.matches = [''.join([text[:-i], m]) for m in
90+
self.attr_matches(methodtext)]
91+
elif dictpattern.match(text):
92+
complete_dict(text)
93+
else:
94+
self.matches = self.global_matches(text)
95+
try:
96+
return self.matches[state]
97+
except IndexError:
98+
return None
99+
57100
def attr_matches(self, text):
58101
"""Taken from rlcompleter.py and bent to my will.
59102
"""

bpython/cli.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -512,15 +512,18 @@ def cw(self):
512512
# isn't at the end of the line, but that's what this does for now.
513513
if self.cpos: return
514514

515-
# look from right to left for a bad method character
515+
# look from right to left for a bad method or dictionary character
516516
l = len(self.s)
517517
is_method_char = lambda c: c.isalnum() or c in ('.', '_')
518+
dict_chars = ['[']
518519

519-
if not self.s or not is_method_char(self.s[l-1]):
520+
if not self.s or not (is_method_char(self.s[-1])
521+
or self.s[-1] in dict_chars):
520522
return
521523

522524
for i in range(1, l+1):
523-
if not is_method_char(self.s[-i]):
525+
c = self.s[-i]
526+
if not (is_method_char(c) or c in dict_chars):
524527
i -= 1
525528
break
526529

@@ -1281,15 +1284,22 @@ def show_list(self, items, topline=None, current_item=None):
12811284
max_h = y + 1
12821285
max_w = int(w * self.config.cli_suggestion_width)
12831286
self.list_win.erase()
1287+
12841288
if items:
12851289
sep = '.'
1286-
if os.path.sep in items[0]:
1287-
# Filename completion
1288-
sep = os.path.sep
1289-
if sep in items[0]:
1290-
items = [x.rstrip(sep).rsplit(sep)[-1] for x in items]
1290+
separators = ['.', os.path.sep, '[']
1291+
lastindex = max([items[0].rfind(c) for c in separators])
1292+
if lastindex > -1:
1293+
sep = items[0][lastindex]
1294+
items = [x.rstrip(sep).rsplit(sep)[-1] for x in items]
1295+
if current_item:
1296+
current_item = current_item.rstrip(sep).rsplit(sep)[-1]
1297+
1298+
if items[0].endswith(']'):
1299+
# dictionary key suggestions
1300+
items = [x.rstrip(']') for x in items]
12911301
if current_item:
1292-
current_item = current_item.rstrip(sep).rsplit(sep)[-1]
1302+
current_item = current_item.rstrip(']')
12931303

12941304
if topline:
12951305
height_offset = self.mkargspec(topline, down) + 1

0 commit comments

Comments
 (0)