3838import sys
3939import os
4040import locale
41+ import signal
4142from types import ModuleType
4243from optparse import Option
4344
@@ -309,17 +310,34 @@ def reprint_line(self, lineno, tokens):
309310 edit .set_edit_markup (list (format_tokens (tokens )))
310311
311312 def push (self , s , insert_into_history = True ):
313+ # Restore the original SIGINT handler. This is needed to be able
314+ # to break out of infinite loops. If the interpreter itself
315+ # sees this it prints 'KeyboardInterrupt' and returns (good).
316+ orig_handler = signal .getsignal (signal .SIGINT )
317+ signal .signal (signal .SIGINT , signal .default_int_handler )
312318 # Pretty blindly adapted from bpython.cli
313319 try :
314320 return repl .Repl .push (self , s , insert_into_history )
315321 except SystemExit :
316322 raise urwid .ExitMainLoop ()
323+ except KeyboardInterrupt :
324+ # KeyboardInterrupt happened between the except block around
325+ # user code execution and this code. This should be rare,
326+ # but make sure to not kill bpython here, so leaning on
327+ # ctrl+c to kill buggy code running inside bpython is safe.
328+ self .keyboard_interrupt ()
329+ finally :
330+ signal .signal (signal .SIGINT , orig_handler )
317331
318332 def start (self ):
319333 # Stolen from bpython.cli again
320334 self .push ('from bpython._internal import _help as help\n ' , False )
321335 self .prompt (False )
322336
337+ def keyboard_interrupt (self ):
338+ # Do we need to do more here? Break out of multiline input perhaps?
339+ self .echo ('KeyboardInterrupt' )
340+
323341 def prompt (self , more ):
324342 # XXX what is s_hist?
325343 if not more :
@@ -382,8 +400,16 @@ def handle_input(self, event):
382400 # XXX what is this s_hist thing?
383401 self .stdout_hist += inp + '\n '
384402 self .edit = None
403+ # This may take a while, so force a redraw first:
404+ self .main_loop .draw_screen ()
385405 more = self .push (inp )
386406 self .prompt (more )
407+ elif event == 'ctrl d' :
408+ # ctrl+d on an empty line exits
409+ if self .edit is not None and not self .edit .get_edit_text ():
410+ raise urwid .ExitMainLoop ()
411+ #else:
412+ # self.echo(repr(event))
387413
388414
389415def main (args = None , locals_ = None , banner = None ):
@@ -461,6 +487,13 @@ def main(args=None, locals_=None, banner=None):
461487 myrepl = URWIDRepl (loop , frame , listbox , overlay , tooltip ,
462488 interpreter , statusbar , config )
463489
490+ if options .reactor :
491+ # Twisted sets a sigInt handler that stops the reactor unless
492+ # it sees a different custom signal handler.
493+ def sigint (* args ):
494+ reactor .callFromThread (myrepl .keyboard_interrupt )
495+ signal .signal (signal .SIGINT , sigint )
496+
464497 # XXX HACK: circular dependency between the event loop and repl.
465498 # Fix by not using unhandled_input?
466499 loop ._unhandled_input = myrepl .handle_input
@@ -515,7 +548,20 @@ def run_find_coroutine():
515548
516549 loop .set_alarm_in (0 , start )
517550
518- loop .run ()
551+ while True :
552+ try :
553+ loop .run ()
554+ except KeyboardInterrupt :
555+ # HACK: if we run under a twisted mainloop this should
556+ # never happen: we have a SIGINT handler set.
557+ # If we use the urwid select-based loop we just restart
558+ # that loop if interrupted, instead of trying to cook
559+ # up an equivalent to reactor.callFromThread (which
560+ # is what our Twisted sigint handler does)
561+ loop .set_alarm_in (0 ,
562+ lambda * args : myrepl .keyboard_interrupt ())
563+ continue
564+ break
519565
520566 if config .hist_length :
521567 histfilename = os .path .expanduser (config .hist_file )
0 commit comments