Skip to content

Commit f8f4e47

Browse files
fix bpython#444 filename completion going too deep
adds some tests of tab key behavior, but doesn't add test for buggy filename completion behavior
1 parent 74382d4 commit f8f4e47

File tree

3 files changed

+84
-6
lines changed

3 files changed

+84
-6
lines changed

bpython/curtsiesfrontend/repl.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -624,33 +624,37 @@ def on_tab(self, back=False):
624624

625625
def only_whitespace_left_of_cursor():
626626
"""returns true if all characters on current line before cursor are whitespace"""
627-
return self.current_line[:self.cursor_offset].strip()
627+
return not self.current_line[:self.cursor_offset].strip()
628628

629629
logger.debug('self.matches_iter.matches: %r', self.matches_iter.matches)
630-
if not only_whitespace_left_of_cursor():
630+
if only_whitespace_left_of_cursor():
631631
front_white = (len(self.current_line[:self.cursor_offset]) -
632632
len(self.current_line[:self.cursor_offset].lstrip()))
633633
to_add = 4 - (front_white % self.config.tab_length)
634634
for _ in range(to_add):
635635
self.add_normal_character(' ')
636636
return
637637

638-
# run complete() if we aren't already iterating through matches
639-
if not self.matches_iter:
638+
#if not self.matches_iter.candidate_selected:
639+
# self.list_win_visible = self.complete(tab=True)
640+
641+
# run complete() if we don't already have matches
642+
if len(self.matches_iter.matches) == 0:
640643
self.list_win_visible = self.complete(tab=True)
641644

642645
# 3. check to see if we can expand the current word
643646
if self.matches_iter.is_cseq():
644647
self._cursor_offset, self._current_line = self.matches_iter.substitute_cseq()
645648
# using _current_line so we don't trigger a completion reset
646-
if not self.matches_iter:
649+
if not self.matches_iter.matches:
647650
self.list_win_visible = self.complete()
648651

649652
elif self.matches_iter.matches:
650653
self.current_match = back and self.matches_iter.previous() \
651654
or self.matches_iter.next()
652655
self._cursor_offset, self._current_line = self.matches_iter.cur_line()
653656
# using _current_line so we don't trigger a completion reset
657+
self.list_win_visible = True
654658

655659
def on_control_d(self):
656660
if self.current_line == '':

bpython/repl.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ def __nonzero__(self):
159159
"""MatchesIterator is False when word hasn't been replaced yet"""
160160
return self.index != -1
161161

162+
@property
163+
def candidate_selected(self):
164+
"""True when word selected/replaced, False when word hasn't been replaced yet"""
165+
return bool(self)
166+
162167
def __iter__(self):
163168
return self
164169

@@ -541,7 +546,7 @@ def complete(self, tab=False):
541546
if len(matches) == 1:
542547
if tab: # if this complete is being run for a tab key press, substitute common sequence
543548
self._cursor_offset, self._current_line = self.matches_iter.substitute_cseq()
544-
return Repl.complete(self)
549+
return Repl.complete(self) # again for
545550
elif self.matches_iter.current_word == matches[0]:
546551
self.matches_iter.clear()
547552
return False

bpython/test/test_curtsies_repl.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import code
33
from contextlib import contextmanager
44
from functools import partial
5+
from mock import Mock
56
import os
67
from StringIO import StringIO
78
import sys
@@ -18,6 +19,7 @@ def skip(f):
1819

1920
from bpython.curtsiesfrontend import repl as curtsiesrepl
2021
from bpython.curtsiesfrontend import interpreter
22+
from bpython import autocomplete
2123
from bpython import config
2224
from bpython import args
2325

@@ -84,6 +86,73 @@ def test_get_last_word_with_prev_line(self):
8486
self.repl.up_one_line()
8587
self.assertEqual(self.repl.current_line,'2 3')
8688

89+
90+
class TestCurtsiesReplTab(unittest.TestCase):
91+
92+
def setUp(self):
93+
self.repl = create_repl()
94+
self.repl.matches_iter = Mock()
95+
def add_matches(*args, **kwargs):
96+
self.repl.matches_iter.matches = ['aaa', 'aab', 'aac']
97+
self.repl.complete = Mock(side_effect=add_matches,
98+
return_value=True)
99+
100+
def test_tab_with_no_matches_triggers_completion(self):
101+
self.repl._current_line = ' asdf'
102+
self.repl._cursor_offset = 5
103+
self.repl.matches_iter.matches = []
104+
self.repl.matches_iter.is_cseq.return_value = False
105+
self.repl.matches_iter.cur_line.return_value = (None, None)
106+
self.repl.on_tab()
107+
self.repl.complete.assert_called_once_with(tab=True)
108+
109+
def test_tab_after_indentation_adds_space(self):
110+
self.repl._current_line = ' '
111+
self.repl._cursor_offset = 4
112+
self.repl.on_tab()
113+
self.assertEqual(self.repl._current_line, ' ')
114+
self.assertEqual(self.repl._cursor_offset, 8)
115+
116+
def test_tab_at_beginning_of_line_adds_space(self):
117+
self.repl._current_line = ''
118+
self.repl._cursor_offset = 0
119+
self.repl.on_tab()
120+
self.assertEqual(self.repl._current_line, ' ')
121+
self.assertEqual(self.repl._cursor_offset, 4)
122+
123+
def test_tab_with_no_matches_selects_first(self):
124+
self.repl._current_line = ' aa'
125+
self.repl._cursor_offset = 3
126+
self.repl.matches_iter.matches = []
127+
self.repl.matches_iter.is_cseq.return_value = False
128+
self.repl.matches_iter.next.return_value = None
129+
self.repl.matches_iter.cur_line.return_value = (None, None)
130+
self.repl.on_tab()
131+
self.repl.complete.assert_called_once_with(tab=True)
132+
self.repl.matches_iter.next.assert_called_once_with()
133+
self.repl.matches_iter.cur_line.assert_called_once_with()
134+
135+
def test_tab_with_matches_selects_next_match(self):
136+
self.repl._current_line = ' aa'
137+
self.repl._cursor_offset = 3
138+
self.repl.complete()
139+
self.repl.matches_iter.is_cseq.return_value = False
140+
self.repl.matches_iter.next.return_value = None
141+
self.repl.matches_iter.cur_line.return_value = (None, None)
142+
self.repl.on_tab()
143+
self.repl.matches_iter.next.assert_called_once_with()
144+
self.repl.matches_iter.cur_line.assert_called_once_with()
145+
146+
def test_tab_completes_common_sequence(self):
147+
self.repl._current_line = ' a'
148+
self.repl._cursor_offset = 2
149+
self.repl.matches_iter.matches = ['aaa', 'aab', 'aac']
150+
self.repl.matches_iter.is_cseq.return_value = True
151+
self.repl.matches_iter.substitute_cseq.return_value = (None, None)
152+
self.repl.on_tab()
153+
self.repl.matches_iter.substitute_cseq.assert_called_once_with()
154+
155+
87156
@contextmanager # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy
88157
def captured_output():
89158
new_out, new_err = StringIO(), StringIO()

0 commit comments

Comments
 (0)