X Tutup
Skip to content

Commit 1a0aa07

Browse files
new autocomplete hooked up, working some
simple completion working in cli filename completion interestingly broken
1 parent 3a1e177 commit 1a0aa07

File tree

7 files changed

+123
-129
lines changed

7 files changed

+123
-129
lines changed

bpython/autocomplete.py

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,10 @@
4848
SUBSTRING = 'substring'
4949
FUZZY = 'fuzzy'
5050

51-
class Autocomplete(rlcompleter.Completer):
52-
def __init__(self, namespace, config):
53-
self.namespace = namespace
54-
self.config = config
55-
def complete(self, cw, _):
56-
self.matches = complete(cw, namespace=self.namespace, config=self.config)
57-
5851
def complete(text, namespace=None, config=None):
5952
"""Return list of matches """
6053
if namespace is None:
61-
namespace = __main__.__dict__
54+
namespace = __main__.__dict__ #TODO figure out if this __main__ still makes sense
6255

6356
if hasattr(config, 'autocomplete_mode'):
6457
autocomplete_mode = config.autocomplete_mode
@@ -182,37 +175,40 @@ def filename_matches(cs):
182175
if cs.startswith('~'):
183176
filename = username + filename[len(user_dir):]
184177
matches.append(filename)
185-
return cs
178+
return matches
186179

187-
def find_matches(cursor_offset, current_line, locals_, current_string_callback, completer, magic_methods, argspec):
180+
def find_matches(cursor_offset, current_line, locals_, argspec, config, magic_methods):
188181
"""Returns a list of matches and function to use for replacing words on tab"""
189182

183+
#TODO use the smarter current_string() in Repl that knows about the buffer
184+
#TODO don't pass in config, pass in the settings themselves
185+
#TODO if importcompletion returns None, that means short circuit return, not
186+
# try something else
190187
if line.current_string(cursor_offset, current_line):
191-
matches = filename_matches(line.current_string(cursor_offset, current_line))
188+
matches = filename_matches(line.current_string(cursor_offset, current_line)[2])
192189
return matches, line.current_string
193190

194191
if line.current_word(cursor_offset, current_line) is None:
195192
return [], None
196193

197194
matches = importcompletion.complete(cursor_offset, current_line)
198-
return matches, line.current_word
195+
if matches:
196+
return matches, line.current_word
199197

200198
cw = line.current_word(cursor_offset, current_line)[2]
201199

202200
try:
203-
completer.complete(cw, 0)
204-
#except Exception:
205-
# # This sucks, but it's either that or list all the exceptions that could
206-
# # possibly be raised here, so if anyone wants to do that, feel free to send me
207-
# # a patch. XXX: Make sure you raise here if you're debugging the completion
208-
# # stuff !
209-
# e = True
210-
#else:
201+
matches = complete(cw, namespace=locals_, config=config)
202+
except Exception:
203+
# This sucks, but it's either that or list all the exceptions that could
204+
# possibly be raised here, so if anyone wants to do that, feel free to send me
205+
# a patch. XXX: Make sure you raise here if you're debugging the completion
206+
# stuff !
207+
e = True
208+
raise
209+
else:
211210
e = False
212-
matches = completer.matches
213211
matches.extend(magic_methods(cw))
214-
except KeyboardInterrupt:
215-
pass
216212

217213
if not e and argspec:
218214
matches.extend(name + '=' for name in argspec[1][0]

bpython/cli.py

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ def complete(self, tab=False):
459459
if self.list_win_visible and not self.config.auto_display_list:
460460
self.scr.touchwin()
461461
self.list_win_visible = False
462-
self.matches_iter.update()
462+
self.matches_iter.clear()
463463
return
464464

465465
if self.config.auto_display_list or tab:
@@ -1484,43 +1484,42 @@ def tab(self, back=False):
14841484
cseq = os.path.commonprefix(seq)
14851485

14861486
if cseq and mode != autocomplete.FUZZY:
1487+
print 'doing cseq'
14871488
expanded_string = cseq[len(cw):]
1488-
self.s += expanded_string
1489-
expanded = bool(expanded_string)
1489+
expanded = bool(expanded_string) #TODO move this logic below to matches_iter
1490+
_, self.s = self.matches_iter.substitute(self.matches_iter.current_word + expanded_string)
14901491
self.print_line(self.s)
14911492
if len(self.matches) == 1 and self.config.auto_display_list:
14921493
self.scr.touchwin()
14931494
if expanded:
1494-
self.matches_iter.update(cseq, self.matches)
1495+
self.matches_iter.update(len(self.s) - self.cpos,
1496+
self.s,
1497+
self.matches)
14951498
else:
14961499
expanded = False
14971500

14981501
# 4. swap current word for a match list item
14991502
if not expanded and self.matches:
1500-
# reset s if this is the nth result
1501-
if self.matches_iter:
1502-
self.s = self.s[:-len(self.matches_iter.current())] + cw
1503+
print 'doing swap'
15031504

15041505
current_match = back and self.matches_iter.previous() \
15051506
or self.matches_iter.next()
15061507

1507-
# update s with the new match
1508-
if current_match:
1509-
try:
1510-
self.show_list(self.matches, self.argspec, current_match)
1511-
except curses.error:
1512-
# XXX: This is a massive hack, it will go away when I get
1513-
# cusswords into a good enough state that we can start
1514-
# using it.
1515-
self.list_win.border()
1516-
self.list_win.refresh()
1517-
1518-
if self.config.autocomplete_mode == autocomplete.SIMPLE:
1519-
self.s += current_match[len(cw):]
1520-
else:
1521-
self.s = self.s[:-len(cw)] + current_match
1508+
try:
1509+
self.show_list(self.matches, self.argspec, current_match)
1510+
except curses.error:
1511+
# XXX: This is a massive hack, it will go away when I get
1512+
# cusswords into a good enough state that we can start
1513+
# using it.
1514+
self.list_win.border()
1515+
self.list_win.refresh()
1516+
1517+
if self.config.autocomplete_mode == autocomplete.SIMPLE:
1518+
_, self.s = self.matches_iter.cur_line()
1519+
else:
1520+
self.s = self.s[:-len(cw)] + current_match
15221521

1523-
self.print_line(self.s, True)
1522+
self.print_line(self.s, True)
15241523
return True
15251524

15261525
def undo(self, n=1):

bpython/curtsiesfrontend/repl.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,7 @@ def paint(self, about_to_exit=False, user_quit=False):
827827
logging.debug('cursor pos: %r', (cursor_row, cursor_column))
828828
return arr, (cursor_row, cursor_column)
829829

830+
830831
@contextlib.contextmanager
831832
def in_paste_mode(self):
832833
orig_value = self.paste_mode

bpython/line.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ def current_string(cursor_offset, line):
4242
4343
Weaker than bpython.Repl's current_string, because that checks that a string is a string
4444
based on previous lines in the buffer"""
45-
matches = list(re.finditer('''(?P<open>(?:""")|"|(?:''\')|')(.*?)(?P=open)''', line))
46-
for m in matches:
47-
if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset:
48-
return m.start(2), m.end(2), m.group(2)
45+
for m in re.finditer('''(?P<open>(?:""")|"|(?:''\')|')(?:((?P<closed>.*?)(?P=open))|(?P<unclosed>.*))''', line):
46+
i = 3 if m.group(3) else 4
47+
if m.start(i) <= cursor_offset and m.end(i) >= cursor_offset:
48+
return m.start(i), m.end(i), m.group(i)
4949
return None
5050

5151
def current_object(cursor_offset, line):

bpython/repl.py

Lines changed: 49 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
from bpython._py3compat import PythonLexer, py3
5151
from bpython.formatter import Parenthesis
5252
from bpython.translations import _
53-
from bpython.autocomplete import Autocomplete
53+
import bpython.autocomplete as autocomplete
5454

5555

5656
# Needed for special handling of __abstractmethods__
@@ -280,6 +280,9 @@ def __init__(self, current_word='', matches=[]):
280280
self.current_word = current_word
281281
self.matches = list(matches)
282282
self.index = -1
283+
self.orig_cursor_offset = None
284+
self.orig_line = None
285+
self.replacer = None
283286

284287
def __nonzero__(self):
285288
return self.index != -1
@@ -303,11 +306,33 @@ def previous(self):
303306

304307
return self.matches[self.index]
305308

306-
def update(self, current_word='', matches=[]):
307-
if current_word != self.current_word:
308-
self.current_word = current_word
309-
self.matches = list(matches)
309+
def cur_line(self):
310+
"""Returns a cursor offset and line pair with the current substitution made"""
311+
return self.substitute(self.current())
312+
313+
def substitute(self, match):
314+
if match.startswith("'"):
315+
raise ValueError(match)
316+
start, end, word = self.replacer(self.orig_cursor_offset, self.orig_line)
317+
result = start + len(match), self.orig_line[:start] + match + self.orig_line[end:]
318+
return result
319+
320+
def update(self, cursor_offset, current_line, matches, replacer=None):
321+
if cursor_offset != self.orig_cursor_offset or current_line != self.orig_line:
322+
if replacer is not None:
323+
self.replacer = replacer
324+
if matches and self.replacer is not None:
325+
self.start, self.end, self.current_word = self.replacer(cursor_offset, current_line)
326+
self.orig_cursor_offset = cursor_offset
327+
self.orig_line = current_line
310328
self.index = -1
329+
self.matches = list(matches)
330+
331+
def clear(self):
332+
self.cursor_offset = -1
333+
self.current_line = ''
334+
self.current_word = ''
335+
self.replacer = None
311336

312337

313338
class Interaction(object):
@@ -378,7 +403,6 @@ def __init__(self, interp, config):
378403
self.s_hist = []
379404
self.history = []
380405
self.evaluating = False
381-
self.completer = Autocomplete(self.interp.locals, config)
382406
self.matches = []
383407
self.matches_iter = MatchesIterator()
384408
self.argspec = None
@@ -580,83 +604,30 @@ def complete(self, tab=False):
580604
(via the inspect module) and bang that on top of the completions too.
581605
The return value is whether the list_win is visible or not."""
582606

583-
#TODO keep factoring out parts of complete
584-
# so we can reuse code when we reimplement complete
585-
# in bpython.curtsiesfrontend.repl
586-
# Maybe move things to autocomplete instead of here?
587-
588607
self.set_docstring()
589608

590-
cw = self.cw()
591-
cs = self.current_string()
592-
if not cw:
593-
self.matches = []
594-
self.matches_iter.update()
595-
if not (cw or cs):
596-
return bool(self.argspec)
597-
598-
if cs and tab:
599-
self.matches = filename_matches(cs)
600-
self.matches_iter.update(cs, self.matches)
601-
return bool(self.matches)
602-
elif cs:
603-
# Do not provide suggestions inside strings, as one cannot tab
604-
# them so they would be really confusing.
605-
self.matches_iter.update()
606-
return False
609+
matches, replacer = autocomplete.find_matches(
610+
len(self.current_line()) - self.cpos,
611+
self.current_line(),
612+
self.interp.locals,
613+
self.argspec,
614+
self.config,
615+
self.magic_method_completions)
607616

608-
# Check for import completion
609-
e = False
610-
matches = importcompletion.complete(len(self.current_line()) - self.cpos, self.current_line())
611-
if matches is not None and not matches:
612-
self.matches = []
613-
self.matches_iter.update()
614-
return False
617+
self.matches = matches
618+
self.matches_iter.update(len(self.current_line()) - self.cpos,
619+
self.current_line(), self.matches, replacer=replacer)
615620

616-
if matches is None:
617-
# Nope, no import, continue with normal completion
618-
try:
619-
self.completer.complete(cw, 0)
620-
except Exception:
621-
# This sucks, but it's either that or list all the exceptions that could
622-
# possibly be raised here, so if anyone wants to do that, feel free to send me
623-
# a patch. XXX: Make sure you raise here if you're debugging the completion
624-
# stuff !
625-
raise
626-
e = True
627-
else:
628-
matches = self.completer.matches
629-
matches.extend(self.magic_method_completions(cw))
630-
631-
if not e and self.argspec:
632-
matches.extend(name + '=' for name in self.argspec[1][0]
633-
if isinstance(name, basestring) and name.startswith(cw))
634-
if py3:
635-
matches.extend(name + '=' for name in self.argspec[1][4]
636-
if name.startswith(cw))
637-
638-
# unless the first character is a _ filter out all attributes starting with a _
639-
if not e and not cw.split('.')[-1].startswith('_'):
640-
matches = [match for match in matches
641-
if not match.split('.')[-1].startswith('_')]
642-
643-
if e or not matches:
644-
self.matches = []
645-
self.matches_iter.update()
646-
if not self.argspec:
647-
return False
621+
if matches is None or len(matches) == 0:
622+
return bool(self.matches or self.argspec)
648623
else:
649-
# remove duplicates
650-
self.matches = sorted(set(matches))
651-
652-
653-
if len(self.matches) == 1 and not self.config.auto_display_list:
654-
self.list_win_visible = True
655-
self.tab()
656-
return False
657-
658-
self.matches_iter.update(cw, self.matches)
659-
return True
624+
if len(self.matches) == 1 and not self.config.auto_display_list:
625+
self.list_win_visible = True
626+
self.tab()
627+
return False
628+
self.matches_iter.update(len(self.current_line()) - self.cpos,
629+
self.current_line(), self.matches, replacer)
630+
return True
660631

661632
def format_docstring(self, docstring, width, height):
662633
"""Take a string and try to format it into a sane list of strings to be

bpython/test/test_autocomplete.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from bpython import autocomplete
2+
from functools import partial
3+
4+
import unittest
5+
6+
class TestSimpleComplete(unittest.TestCase):
7+
8+
simple_config = type('', (), {})()
9+
simple_config.autocomplete_mode = autocomplete.SIMPLE
10+
complete = partial(autocomplete.complete,
11+
namespace={'zabcdef':1, 'zabcqwe':2, 'ze':3},
12+
config=simple_config)
13+
14+
def test_simple_completion(self):
15+
self.assertEqual(self.complete('zab'), ['zabcdef', 'zabcqwe'])
16+
self.assertEqual(self.complete('zabc'), ['zabcdef', 'zabcqwe'])
17+
self.assertEqual(self.complete('zabcd'), ['zabcdef'])
18+

bpython/test/test_line_properties.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def test_simple(self):
146146
class TestCurrentString(LineTestCase):
147147
def setUp(self):
148148
self.func = current_string
149-
def test_simple(self):
149+
def test_closed(self):
150150
self.assertAccess('"<as|df>"')
151151
self.assertAccess('"<asdf|>"')
152152
self.assertAccess('"<|asdf>"')
@@ -155,6 +155,15 @@ def test_simple(self):
155155
self.assertAccess("'''<asdf|>'''")
156156
self.assertAccess('"""<asdf|>"""')
157157
self.assertAccess('asdf.afd("a") + "<asdf|>"')
158+
def test_open(self):
159+
self.assertAccess('"<as|df>')
160+
self.assertAccess('"<asdf|>')
161+
self.assertAccess('"<|asdf>')
162+
self.assertAccess("'<asdf|>")
163+
self.assertAccess("'<|asdf>")
164+
self.assertAccess("'''<asdf|>")
165+
self.assertAccess('"""<asdf|>')
166+
self.assertAccess('asdf.afd("a") + "<asdf|>')
158167

159168
class TestCurrentObject(LineTestCase):
160169
def setUp(self):

0 commit comments

Comments
 (0)
X Tutup