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)
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