Skip to content

Commit e318b6b

Browse files
first draft of incremental search (ctrl-r/s)
1 parent fc1cf63 commit e318b6b

File tree

3 files changed

+68
-15
lines changed

3 files changed

+68
-15
lines changed

bpython/curtsiesfrontend/interaction.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def has_focus(self):
5151
return self.in_prompt or self.in_confirm or self.waiting_for_refresh
5252

5353
def message(self, msg):
54+
"""Sets a temporary message"""
5455
self.message_start_time = time.time()
5556
self._message = msg
5657
self.refresh_request(time.time() + self.message_time)

bpython/curtsiesfrontend/repl.py

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -298,16 +298,19 @@ def smarter_request_reload(desc):
298298
self.stdin = FakeStdin(self.coderunner, self, self.edit_keys)
299299

300300
self.request_paint_to_clear_screen = False # next paint should clear screen
301-
self.last_events = [None] * 50
302-
self.presentation_mode = False
303-
self.paste_mode = False
304-
self.current_match = None
305-
self.list_win_visible = False
306-
self.watching_files = False
301+
self.last_events = [None] * 50 # some commands act differently based on the prev event
302+
# this list doesn't include instances of event.Event,
303+
# only keypress-type events (no refresh screen events etc.)
304+
self.presentation_mode = False # displays prev events in a column on the right hand side
305+
self.paste_mode = False # currently processing a paste event
306+
self.current_match = None # currently tab-selected autocompletion suggestion
307+
self.list_win_visible = False # whether the infobox (suggestions, docstring) is visible
308+
self.watching_files = False # auto reloading turned on
309+
self.special_mode = None # 'reverse_incremental_search' and 'incremental_search'
307310

308311
self.original_modules = sys.modules.keys()
309312

310-
self.width = None # will both be set by a window resize event
313+
self.width = None
311314
self.height = None
312315

313316
self.status_bar.message(banner)
@@ -460,6 +463,12 @@ def process_key_event(self, e):
460463
self.down_one_line()
461464
elif e in ("<Ctrl-d>",):
462465
self.on_control_d()
466+
elif e in ("<Ctrl-r>",):
467+
self.incremental_search(reverse=True)
468+
elif e in ("<Ctrl-s>",):
469+
self.incremental_search()
470+
elif e in ("<BACKSPACE>", '<Ctrl-h>') and self.special_mode:
471+
self.add_to_incremental_search(self, backspace=True)
463472
elif e in self.edit_keys.cut_buffer_edits:
464473
self.readline_kill(e)
465474
elif e in self.edit_keys.simple_edits:
@@ -507,6 +516,21 @@ def process_key_event(self, e):
507516
else:
508517
self.add_normal_character(e)
509518

519+
def incremental_search(self, reverse=False):
520+
if self.special_mode == None:
521+
current_line = ''
522+
if reverse:
523+
self.special_mode = 'reverse_incremental_search'
524+
else:
525+
self.special_mode = 'incremental_search'
526+
else:
527+
self._set_current_line(self.rl_history.back(False, search=True)
528+
if reverse else
529+
self.rl_history.forward(False, search=True),
530+
reset_rl_history=False, clear_special_mode=False)
531+
self._set_cursor_offset(len(self.current_line), reset_rl_history=False,
532+
clear_special_mode=False)
533+
510534
def readline_kill(self, e):
511535
func = self.edit_keys[e]
512536
self.cursor_offset, self.current_line, cut = func(self.cursor_offset, self.current_line)
@@ -659,14 +683,35 @@ def toggle_file_watch(self):
659683
def add_normal_character(self, char):
660684
if len(char) > 1 or is_nop(char):
661685
return
662-
self.current_line = (self.current_line[:self.cursor_offset] +
663-
char +
664-
self.current_line[self.cursor_offset:])
665-
self.cursor_offset += 1
686+
if self.special_mode == 'reverse_incremental_search':
687+
self.add_to_incremental_search(char)
688+
else:
689+
self.current_line = (self.current_line[:self.cursor_offset] +
690+
char +
691+
self.current_line[self.cursor_offset:])
692+
self.cursor_offset += 1
666693
if self.config.cli_trim_prompts and self.current_line.startswith(self.ps1):
667694
self.current_line = self.current_line[4:]
668695
self.cursor_offset = max(0, self.cursor_offset - 4)
669696

697+
def add_to_incremental_search(self, char=None, backspace=False):
698+
if char is None and not backspace:
699+
raise ValueError("must provide a char or set backspace to True")
700+
saved_line = self.rl_history.saved_line
701+
if backspace:
702+
saved_line = saved_line[:-1]
703+
else:
704+
saved_line += char
705+
self.update_completion()
706+
self.rl_history.reset()
707+
self.rl_history.enter(saved_line)
708+
if self.special_mode == 'reverse_incremental_search':
709+
self.incremental_search(reverse=True)
710+
elif self.special_mode == 'incremental_search':
711+
self.incremental_search()
712+
else:
713+
raise ValueError('add_to_incremental_search should only be called in a special mode')
714+
670715
def update_completion(self, tab=False):
671716
"""Update visible docstring and matches, and possibly hide/show completion box"""
672717
#Update autocomplete info; self.matches_iter and self.argspec
@@ -866,6 +911,9 @@ def display_buffer_lines(self):
866911
@property
867912
def display_line_with_prompt(self):
868913
"""colored line with prompt"""
914+
if self.special_mode == 'reverse_incremental_search':
915+
return func_for_letter(self.config.color_scheme['prompt'])(
916+
'(reverse-i-search)`%s\': ' % (self.rl_history.saved_line,)) + self.current_line_formatted
869917
return (func_for_letter(self.config.color_scheme['prompt'])(self.ps1)
870918
if self.done else
871919
func_for_letter(self.config.color_scheme['prompt_more'])(self.ps2)) + self.current_line_formatted
@@ -1085,21 +1133,25 @@ def __repr__(self):
10851133

10861134
def _get_current_line(self):
10871135
return self._current_line
1088-
def _set_current_line(self, line, update_completion=True, reset_rl_history=True):
1136+
def _set_current_line(self, line, update_completion=True, reset_rl_history=True, clear_special_mode=True):
10891137
self._current_line = line
10901138
if update_completion:
10911139
self.update_completion()
10921140
if reset_rl_history:
10931141
self.rl_history.reset()
1142+
if clear_special_mode:
1143+
self.special_mode = None
10941144
current_line = property(_get_current_line, _set_current_line, None,
10951145
"The current line")
10961146
def _get_cursor_offset(self):
10971147
return self._cursor_offset
1098-
def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=True):
1148+
def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=True, clear_special_mode=True):
10991149
if update_completion:
11001150
self.update_completion()
11011151
if reset_rl_history:
11021152
self.rl_history.reset()
1153+
if clear_special_mode:
1154+
self.special_mode = None
11031155
self._cursor_offset = offset
11041156
self.update_completion()
11051157
cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None,

bpython/test/test_curtsies_painting.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ def test_startup(self):
3838

3939
def test_enter_text(self):
4040
[self.repl.add_normal_character(c) for c in '1 + 1']
41-
screen = fsarray([cyan('>>> ') + bold(green('1')+cyan(' ')+
42-
yellow('+') + cyan(' ') + green('1')), cyan('Welcome to')])
41+
screen = fsarray([cyan('>>> ') + bold(blue('1')+cyan(' ')+
42+
yellow('+') + cyan(' ') + green('1')), cyan('welcome')])
4343
self.assert_paint(screen, (0, 9))
4444

4545
def test_run_line(self):

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy