Skip to content

Commit 9aff5bc

Browse files
committed
First stage of Interaction abstraction. All frontends now implement repl.Interaction to show messages and generally interact with the user (prompts, notices, save prompts)
1 parent 48b6fc1 commit 9aff5bc

File tree

4 files changed

+93
-44
lines changed

4 files changed

+93
-44
lines changed

bpython/cli.py

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@
5858
# This for keys
5959
from bpython.keys import key_dispatch
6060

61+
from bpython import repl
6162
from bpython.pager import page
62-
from bpython.repl import Interpreter, Repl
6363
import bpython.args
6464

6565

@@ -233,10 +233,25 @@ def make_colors(config):
233233
return c
234234

235235

236-
class CLIRepl(Repl):
236+
class CLIInteraction(repl.Interaction):
237+
def __init__(self, config, statusbar=None):
238+
repl.Interaction.__init__(self, config, statusbar)
239+
240+
def confirm(self, q):
241+
"""Ask for yes or no and return boolean"""
242+
return self.statusbar.prompt(q).lower().startswith('y')
243+
244+
def notify(self, s, n=10):
245+
return self.statusbar.message(s, n)
246+
247+
def file_prompt(self, s):
248+
return self.statusbar.prompt(s)
249+
250+
251+
class CLIRepl(repl.Repl):
237252

238253
def __init__(self, scr, interp, statusbar, config, idle=None):
239-
Repl.__init__(self, interp, config)
254+
repl.Repl.__init__(self, interp, config)
240255
interp.writetb = self.writetb
241256
self.scr = scr
242257
self.stdout_hist = ''
@@ -251,6 +266,7 @@ def __init__(self, scr, interp, statusbar, config, idle=None):
251266
self.s = ''
252267
self.statusbar = statusbar
253268
self.formatter = BPythonFormatter(config.color_scheme)
269+
self.interact = CLIInteraction(self.config, statusbar=self.statusbar)
254270

255271
def addstr(self, s):
256272
"""Add a string to the current input line and figure out
@@ -334,7 +350,7 @@ def clear_current_line(self):
334350
"""Called when a SyntaxError occured in the interpreter. It is
335351
used to prevent autoindentation from occuring after a
336352
traceback."""
337-
Repl.clear_current_line(self)
353+
repl.Repl.clear_current_line(self)
338354
self.s = ''
339355

340356
def clear_wrapped_lines(self):
@@ -359,7 +375,7 @@ def complete(self, tab=False):
359375
return
360376

361377
if self.config.auto_display_list or tab:
362-
self.list_win_visible = Repl.complete(self, tab)
378+
self.list_win_visible = repl.Repl.complete(self, tab)
363379
if self.list_win_visible:
364380
try:
365381
self.show_list(self.matches, self.argspec)
@@ -900,7 +916,7 @@ def push(self, s, insert_into_history=True):
900916
# curses.raw(True) prevents C-c from causing a SIGINT
901917
curses.raw(False)
902918
try:
903-
return Repl.push(self, s, insert_into_history)
919+
return repl.Repl.push(self, s, insert_into_history)
904920
except SystemExit:
905921
# Avoid a traceback on e.g. quit()
906922
self.do_exit = True
@@ -998,10 +1014,7 @@ def resize(self):
9981014
self.statusbar.resize(refresh=False)
9991015
self.redraw()
10001016

1001-
def ask_confirmation(self, q):
1002-
"""Ask for yes or no and return boolean"""
1003-
return self.statusbar.prompt(q).lower().startswith('y')
1004-
1017+
10051018
def getstdout(self):
10061019
"""This method returns the 'spoofed' stdout buffer, for writing to a
10071020
file or sending to a pastebin or whatever."""
@@ -1293,7 +1306,7 @@ def tab(self, back=False):
12931306
return True
12941307

12951308
def undo(self, n=1):
1296-
Repl.undo(self, n)
1309+
repl.Repl.undo(self, n)
12971310

12981311
# This will unhighlight highlighted parens
12991312
self.print_line(self.s)
@@ -1613,6 +1626,7 @@ def main_curses(scr, args, config, interactive=True, locals_=None,
16131626
global stdscr
16141627
global DO_RESIZE
16151628
global colors
1629+
global repl
16161630
DO_RESIZE = False
16171631

16181632
old_sigwinch_handler = signal.signal(signal.SIGWINCH,
@@ -1639,10 +1653,10 @@ def main_curses(scr, args, config, interactive=True, locals_=None,
16391653
if locals_ is None:
16401654
sys.modules['__main__'] = ModuleType('__main__')
16411655
locals_ = sys.modules['__main__'].__dict__
1642-
interpreter = Interpreter(locals_, getpreferredencoding())
1656+
interpreter = repl.Interpreter(locals_, getpreferredencoding())
16431657

1644-
repl = CLIRepl(main_win, interpreter, statusbar, config, idle)
1645-
repl._C = cols
1658+
clirepl = CLIRepl(main_win, interpreter, statusbar, config, idle)
1659+
clirepl._C = cols
16461660

16471661
sys.stdin = FakeStdin(repl)
16481662
sys.stdout = repl
@@ -1652,18 +1666,18 @@ def main_curses(scr, args, config, interactive=True, locals_=None,
16521666
bpython.args.exec_code(interpreter, args)
16531667
if not interactive:
16541668
curses.raw(False)
1655-
return repl.getstdout()
1669+
return clirepl.getstdout()
16561670
else:
16571671
sys.path.insert(0, '')
1658-
repl.startup()
1672+
clirepl.startup()
16591673

16601674
if banner is not None:
1661-
repl.write(banner)
1662-
repl.write('\n')
1663-
repl.repl()
1675+
clirepl.write(banner)
1676+
clirepl.write('\n')
1677+
clirepl.repl()
16641678
if config.hist_length:
16651679
histfilename = os.path.expanduser(config.hist_file)
1666-
repl.rl_history.save(histfilename, getpreferredencoding())
1680+
clirepl.rl_history.save(histfilename, getpreferredencoding())
16671681

16681682
main_win.erase()
16691683
main_win.refresh()

bpython/gtk_.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,11 @@ class Statusbar(gtk.Statusbar):
140140
def __init__(self):
141141
gtk.Statusbar.__init__(self)
142142

143-
self.context_id = self.get_context_id('StatusBar')
143+
self.context_id = self.get_context_id('Statusbar')
144144

145145
def message(self, s, n=3):
146146
self.push(self.context_id, s)
147147

148-
def prompt(self, s):
149-
pass
150-
151148

152149
class SuggestionWindow(gtk.Window):
153150
"""
@@ -267,6 +264,20 @@ def update_matches(self, matches):
267264
self.view.set_property('visible', bool(matches))
268265

269266

267+
class GTKInteraction(repl.Interaction):
268+
def __init__(self, config, statusbar):
269+
repl.Interaction.__init__(self, config, statusbar)
270+
271+
def confirm(self, q):
272+
dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO, q)
273+
response = True if dialog.run() == gtk.RESPONSE_YES else False
274+
dialog.destroy()
275+
return response
276+
277+
def notify(self, s, n=10):
278+
self.statusbar.message(s)
279+
280+
270281
class ReplWidget(gtk.TextView, repl.Repl):
271282
__gsignals__ = dict(button_press_event=None,
272283
focus_in_event=None,
@@ -290,7 +301,7 @@ def __init__(self, interpreter, config):
290301
self.modify_base('normal', gtk.gdk.color_parse(_COLORS[self.config.color_gtk_scheme['background']]))
291302

292303
self.text_buffer = self.get_buffer()
293-
self.statusbar = Statusbar()
304+
self.interact = GTKInteraction(self.config, Statusbar())
294305
tags = dict()
295306
for (name, value) in self.config.color_gtk_scheme.iteritems():
296307
tag = tags[name] = self.text_buffer.create_tag(name)
@@ -574,12 +585,6 @@ def on_suggestion_selection_changed(self, selection, word):
574585
self.text_buffer.insert_at_cursor(word)
575586

576587

577-
def ask_confirmation(self, q):
578-
dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO, q)
579-
response = True if dialog.run() == gtk.RESPONSE_YES else False
580-
dialog.destroy()
581-
return response
582-
583588
def do_paste(self, widget):
584589
self.pastebin()
585590

@@ -772,7 +777,7 @@ def main(args=None):
772777
sw.add(repl_widget)
773778
container.add(sw)
774779

775-
sb = repl_widget.statusbar
780+
sb = repl_widget.interact.statusbar
776781
container.pack_end(sb, expand=False)
777782

778783
parent.show_all()
@@ -788,7 +793,6 @@ def main(args=None):
788793
repl_widget.rl_history.save(histfilename, getpreferredencoding())
789794
return 0
790795

791-
792796
if __name__ == '__main__':
793797
from bpython.gtk_ import main
794798
main()

bpython/repl.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,23 @@ def update(self, current_word='', matches=[]):
236236
self.index = -1
237237

238238

239+
class Interaction(object):
240+
def __init__(self, config, statusbar=None):
241+
self.config = config
242+
243+
if statusbar:
244+
self.statusbar = statusbar
245+
246+
def confirm(self, s):
247+
raise NotImplementedError
248+
249+
def notify(self, s, n=10):
250+
raise NotImplementedError
251+
252+
def file_prompt(self, s):
253+
raise NotImplementedError
254+
255+
239256
class Repl(object):
240257
"""Implements the necessary guff for a Python-repl-alike interface
241258
@@ -302,6 +319,7 @@ def __init__(self, interp, config):
302319
self.list_win_visible = False
303320
self._C = {}
304321
self.prev_block_finished = 0
322+
self.interact = Interaction(self.config)
305323
# previous pastebin content to prevent duplicate pastes, filled on call
306324
# to repl.pastebin
307325
self.prev_pastebin_content = ''
@@ -615,9 +633,9 @@ def write2file(self):
615633
buffer to disk."""
616634

617635
try:
618-
fn = self.statusbar.prompt('Save to file (Esc to cancel): ')
636+
fn = self.interact.file_prompt('Save to file (Esc to cancel): ')
619637
except ValueError:
620-
self.statusbar.message("Save cancelled.")
638+
self.interact.notify("Save cancelled.")
621639
return
622640

623641
if fn.startswith('~'):
@@ -630,9 +648,9 @@ def write2file(self):
630648
f.write(s)
631649
f.close()
632650
except IOError:
633-
self.statusbar.message("Disk write error for file '%s'." % (fn, ))
651+
self.interact.notify("Disk write error for file '%s'." % (fn, ))
634652
else:
635-
self.statusbar.message('Saved to %s' % (fn, ))
653+
self.interact.notify('Saved to %s' % (fn, ))
636654

637655
def pastebin(self, s=None):
638656
"""Upload to a pastebin and display the URL in the status bar."""
@@ -641,31 +659,31 @@ def pastebin(self, s=None):
641659
s = self.getstdout()
642660

643661
if (self.config.pastebin_confirm and
644-
not self.ask_confirmation("Pastebin buffer? (y/N) ")):
645-
self.statusbar.message("Pastebin aborted")
662+
not self.interact.confirm("Pastebin buffer? (y/N) ")):
663+
self.interact.notify("Pastebin aborted")
646664
return
647665

648666
pasteservice = ServerProxy(self.config.pastebin_url)
649667

650668
if s == self.prev_pastebin_content:
651-
self.statusbar.message('Duplicate pastebin. Previous URL: ' +
652-
self.prev_pastebin_url)
669+
self.interact.notify('Duplicate pastebin. Previous URL: ' +
670+
self.prev_pastebin_url)
653671
return
654672

655673
self.prev_pastebin_content = s
656674

657-
self.statusbar.message('Posting data to pastebin...')
675+
self.interact.notify('Posting data to pastebin...')
658676
try:
659677
paste_id = pasteservice.pastes.newPaste('pycon', s)
660678
except XMLRPCError, e:
661-
self.statusbar.message('Upload failed: %s' % (str(e), ) )
679+
self.interact.notify('Upload failed: %s' % (str(e), ) )
662680
return
663681

664682
paste_url_template = Template(self.config.pastebin_show_url)
665683
paste_id = urlquote(paste_id)
666684
paste_url = paste_url_template.safe_substitute(paste_id=paste_id)
667685
self.prev_pastebin_url = paste_url
668-
self.statusbar.message('Pastebin URL: %s' % (paste_url, ), 10)
686+
self.interact.notify('Pastebin URL: %s' % (paste_url, ), 10)
669687

670688
def push(self, s, insert_into_history=True):
671689
"""Push a line of code onto the buffer so it can process it all

bpython/urwid.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,17 @@ def render(self, size, focus=False):
384384
canvas.cursor = cursor
385385
return canvas
386386

387+
class URWIDInteraction(repl.Interaction):
388+
def __init__(self, config, statusbar=None):
389+
repl.Interaction.__init__(self, config, statusbar)
390+
391+
def confirm(self, q):
392+
"""Ask for yes or no and return boolean"""
393+
return self.statusbar.prompt(q).lower().startswith('y')
394+
395+
def notify(self, s, n=10):
396+
return self.statusbar.message(s, n)
397+
387398

388399
class URWIDRepl(repl.Repl):
389400

@@ -400,6 +411,8 @@ def __init__(self, event_loop, palette, interpreter, config):
400411
config.pastebin_key, config.last_output_key,
401412
config.show_source_key))
402413

414+
self.interact = URWIDInteraction(self.config, self.statusbar)
415+
403416
self.tooltip = urwid.ListBox(urwid.SimpleListWalker([]))
404417
self.tooltip.grid = None
405418
self.overlay = Tooltip(self.listbox, self.tooltip)

0 commit comments

Comments
 (0)