Skip to content

Commit 52d2fd7

Browse files
author
Sebastian Ramacher
committed
Implement a prompt for the urwid interface.
1 parent 6f6fa93 commit 52d2fd7

File tree

1 file changed

+110
-33
lines changed

1 file changed

+110
-33
lines changed

bpython/urwid.py

Lines changed: 110 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,28 @@ def wrapper(*args, **kwargs):
147147
TwistedEventLoop = urwid.TwistedEventLoop
148148

149149

150+
class StatusbarEdit(urwid.Edit):
151+
"""Wrapper around urwid.Edit used for the prompt in Statusbar.
152+
153+
This class only adds a single signal that is emitted if the user presses
154+
Enter."""
155+
156+
signals = urwid.Edit.signals + ['prompt_enter']
157+
158+
def __init__(self, *args, **kwargs):
159+
self.single = False
160+
urwid.Edit.__init__(self, *args, **kwargs)
161+
162+
def keypress(self, size, key):
163+
if self.single:
164+
urwid.emit_signal(self, 'prompt_enter', self, key)
165+
elif key == 'enter':
166+
urwid.emit_signal(self, 'prompt_enter', self, self.get_edit_text())
167+
else:
168+
return urwid.Edit.keypress(self, size, key)
169+
170+
urwid.register_signal(StatusbarEdit, 'prompt_enter')
171+
150172
class Statusbar(object):
151173

152174
"""Statusbar object, ripped off from bpython.cli.
@@ -167,15 +189,22 @@ class Statusbar(object):
167189
The "widget" attribute is an urwid widget.
168190
"""
169191

192+
signals = ['prompt_result']
193+
170194
def __init__(self, config, s=None, main_loop=None):
171195
self.config = config
172196
self.timer = None
173197
self.main_loop = main_loop
174198
self.s = s or ''
175199

176-
self.widget = urwid.Text(('main', self.s))
200+
self.text = urwid.Text(('main', self.s))
177201
# use wrap mode 'clip' to just cut off at the end of line
178-
self.widget.set_wrap_mode('clip')
202+
self.text.set_wrap_mode('clip')
203+
204+
self.edit = StatusbarEdit(('main', ''))
205+
urwid.connect_signal(self.edit, 'prompt_enter', self._on_prompt_enter)
206+
207+
self.widget = urwid.Columns([self.text, self.edit])
179208

180209
def _check(self, callback, userdata=None):
181210
"""This is the method is called from the timer to reset the status bar."""
@@ -189,30 +218,57 @@ def message(self, s, n=3):
189218
self.settext(s)
190219
self.timer = self.main_loop.set_alarm_in(n, self._check)
191220

192-
def prompt(self, s=''):
193-
"""Prompt the user for some input (with the optional prompt 's') and
194-
return the input text, then restore the statusbar to its original
195-
value."""
221+
def prompt(self, s=None, single=False):
222+
"""Prompt the user for some input (with the optional prompt 's'). After
223+
the user hit enter the signal 'prompt_result' will be emited and the
224+
status bar will be reset. If single is True, the first keypress will be
225+
returned."""
196226

197-
# TODO
198-
return ''
227+
if self.timer is not None:
228+
self.main_loop.remove_alarm(self.timer)
229+
self.timer = None
230+
231+
self.edit.single = single
232+
self.edit.set_caption(('main', s or '?'))
233+
self.edit.set_edit_text('')
234+
# hide the text and display the edit widget
235+
if not self.edit in self.widget.widget_list:
236+
self.widget.widget_list.append(self.edit)
237+
if self.text in self.widget.widget_list:
238+
self.widget.widget_list.remove(self.text)
239+
self.widget.set_focus_column(0)
199240

200241
def settext(self, s, permanent=False):
201242
"""Set the text on the status bar to a new value. If permanent is True,
202-
the new value will be permanent."""
243+
the new value will be permanent. If that status bar is in prompt mode,
244+
the prompt will be aborted. """
203245

204246
if self.timer is not None:
205247
self.main_loop.remove_alarm(self.timer)
206248
self.timer = None
207249

208-
self.widget.set_text(('main', s))
250+
# hide the edit and display the text widget
251+
if self.edit in self.widget.widget_list:
252+
self.widget.widget_list.remove(self.edit)
253+
if not self.text in self.widget.widget_list:
254+
self.widget.widget_list.append(self.text)
255+
256+
self.text.set_text(('main', s))
209257
if permanent:
210258
self.s = s
211259

212260
def clear(self):
213261
"""Clear the status bar."""
214262
self.settext('')
215263

264+
def _on_prompt_enter(self, edit, new_text):
265+
"""Reset the statusbar and pass the input from the prompt to the caller
266+
via 'prompt_result'."""
267+
self.settext(self.s)
268+
urwid.emit_signal(self, 'prompt_result', new_text)
269+
270+
urwid.register_signal(Statusbar, 'prompt_result')
271+
216272

217273
def decoding_input_filter(keys, raw):
218274
"""Input filter for urwid which decodes each key with the locale's
@@ -269,7 +325,7 @@ def __init__(self, config, *args, **kwargs):
269325
self._bpy_selectable = True
270326
self._bpy_may_move_cursor = False
271327
self.config = config
272-
self.tab_length = config.tab_length
328+
self.tab_length = config.tab_length
273329
urwid.Edit.__init__(self, *args, **kwargs)
274330

275331
def set_edit_pos(self, pos):
@@ -353,9 +409,6 @@ def keypress(self, size, key):
353409
self.set_edit_text(line[:-self.tab_length])
354410
else:
355411
return urwid.Edit.keypress(self, size, key)
356-
elif key == 'pastebin':
357-
# do pastebin
358-
pass
359412
else:
360413
# TODO: Add in specific keypress fetching code here
361414
return urwid.Edit.keypress(self, size, key)
@@ -461,21 +514,40 @@ def render(self, size, focus=False):
461514
return canvas
462515

463516
class URWIDInteraction(repl.Interaction):
464-
def __init__(self, config, statusbar=None):
517+
def __init__(self, config, statusbar, frame):
465518
repl.Interaction.__init__(self, config, statusbar)
519+
self.frame = frame
520+
urwid.connect_signal(statusbar, 'prompt_result', self._prompt_result)
521+
self.callback = None
466522

467-
def confirm(self, q):
468-
"""Ask for yes or no and return boolean"""
469-
try:
470-
reply = self.statusbar.prompt(q)
471-
except ValueError:
472-
return False
523+
def confirm(self, q, callback):
524+
"""Ask for yes or no and call callback to return the result"""
525+
526+
def callback_wrapper(result):
527+
callback(result.lower() in (_('y'), _('yes')))
473528

474-
return reply.lower() in (_('y'), _('yes'))
529+
self.prompt(q, callback_wrapper, single=True)
475530

476531
def notify(self, s, n=10):
477532
return self.statusbar.message(s, n)
478533

534+
def prompt(self, s, callback=None, single=False):
535+
"""Prompt the user for input. The result will be returned via calling
536+
callback."""
537+
538+
if self.callback is not None:
539+
raise Exception('Prompt already in progress')
540+
541+
self.callback = callback
542+
self.statusbar.prompt(s, single=single)
543+
self.frame.set_focus('footer')
544+
545+
def _prompt_result(self, text):
546+
self.frame.set_focus('body')
547+
if self.callback is not None:
548+
self.callback(text)
549+
self.callback = None
550+
479551

480552
class URWIDRepl(repl.Repl):
481553

@@ -490,21 +562,12 @@ def __init__(self, event_loop, palette, interpreter, config):
490562

491563
self.listbox = BPythonListBox(urwid.SimpleListWalker([]))
492564

493-
# String is straight from bpython.cli
494-
self.statusbar = Statusbar(config,
495-
_(" <%s> Rewind <%s> Save <%s> Pastebin "
496-
" <%s> Pager <%s> Show Source ") %
497-
(config.undo_key, config.save_key, config.pastebin_key,
498-
config.last_output_key, config.show_source_key))
499-
500-
self.interact = URWIDInteraction(self.config, self.statusbar)
501-
502565
self.tooltip = urwid.ListBox(urwid.SimpleListWalker([]))
503566
self.tooltip.grid = None
504567
self.overlay = Tooltip(self.listbox, self.tooltip)
505568
self.stdout_hist = ''
506569

507-
self.frame = urwid.Frame(self.overlay, footer=self.statusbar.widget)
570+
self.frame = urwid.Frame(self.overlay)
508571

509572
if urwid.get_encoding_mode() == 'narrow':
510573
input_filter = decoding_input_filter
@@ -516,7 +579,15 @@ def __init__(self, event_loop, palette, interpreter, config):
516579
self.frame, palette,
517580
event_loop=event_loop, unhandled_input=self.handle_input,
518581
input_filter=input_filter, handle_mouse=False)
519-
self.statusbar.main_loop = self.main_loop
582+
583+
# String is straight from bpython.cli
584+
self.statusbar = Statusbar(config,
585+
_(" <%s> Rewind <%s> Save <%s> Pastebin "
586+
" <%s> Pager <%s> Show Source ") %
587+
(config.undo_key, config.save_key, config.pastebin_key,
588+
config.last_output_key, config.show_source_key), self.main_loop)
589+
self.frame.set_footer(self.statusbar.widget)
590+
self.interact = URWIDInteraction(self.config, self.statusbar, self.frame)
520591

521592
self.edits = []
522593
self.edit = None
@@ -899,6 +970,11 @@ def on_edit_pos_changed(self, edit, position):
899970
edit.set_edit_markup(list(format_tokens(tokens)))
900971

901972
def handle_input(self, event):
973+
# Since most of the input handling here should be handled in the edit
974+
# instead, we return here early if the edit doesn't have the focus.
975+
if self.frame.get_focus() != 'body':
976+
return
977+
902978
if event == 'enter':
903979
inp = self.edit.get_edit_text()
904980
self.history.append(inp)
@@ -1189,6 +1265,7 @@ def load_urwid_command_map(config):
11891265
urwid.command_map[key_dispatch[config.down_one_line_key]] = 'cursor down'
11901266
urwid.command_map[key_dispatch['C-a']] = 'cursor max left'
11911267
urwid.command_map[key_dispatch['C-e']] = 'cursor max right'
1268+
urwid.command_map[key_dispatch[config.pastebin_key]] = 'pastebin'
11921269
"""
11931270
'clear_line': 'C-u',
11941271
'clear_screen': 'C-l',

0 commit comments

Comments
 (0)