|
38 | 38 | import sys
|
39 | 39 | import os
|
40 | 40 | import locale
|
| 41 | +import signal |
41 | 42 | from types import ModuleType
|
42 | 43 | from optparse import Option
|
43 | 44 |
|
@@ -309,17 +310,34 @@ def reprint_line(self, lineno, tokens):
|
309 | 310 | edit.set_edit_markup(list(format_tokens(tokens)))
|
310 | 311 |
|
311 | 312 | 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) |
312 | 318 | # Pretty blindly adapted from bpython.cli
|
313 | 319 | try:
|
314 | 320 | return repl.Repl.push(self, s, insert_into_history)
|
315 | 321 | except SystemExit:
|
316 | 322 | 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) |
317 | 331 |
|
318 | 332 | def start(self):
|
319 | 333 | # Stolen from bpython.cli again
|
320 | 334 | self.push('from bpython._internal import _help as help\n', False)
|
321 | 335 | self.prompt(False)
|
322 | 336 |
|
| 337 | + def keyboard_interrupt(self): |
| 338 | + # Do we need to do more here? Break out of multiline input perhaps? |
| 339 | + self.echo('KeyboardInterrupt') |
| 340 | + |
323 | 341 | def prompt(self, more):
|
324 | 342 | # XXX what is s_hist?
|
325 | 343 | if not more:
|
@@ -382,8 +400,16 @@ def handle_input(self, event):
|
382 | 400 | # XXX what is this s_hist thing?
|
383 | 401 | self.stdout_hist += inp + '\n'
|
384 | 402 | self.edit = None
|
| 403 | + # This may take a while, so force a redraw first: |
| 404 | + self.main_loop.draw_screen() |
385 | 405 | more = self.push(inp)
|
386 | 406 | 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)) |
387 | 413 |
|
388 | 414 |
|
389 | 415 | def main(args=None, locals_=None, banner=None):
|
@@ -461,6 +487,13 @@ def main(args=None, locals_=None, banner=None):
|
461 | 487 | myrepl = URWIDRepl(loop, frame, listbox, overlay, tooltip,
|
462 | 488 | interpreter, statusbar, config)
|
463 | 489 |
|
| 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 | + |
464 | 497 | # XXX HACK: circular dependency between the event loop and repl.
|
465 | 498 | # Fix by not using unhandled_input?
|
466 | 499 | loop._unhandled_input = myrepl.handle_input
|
@@ -515,7 +548,20 @@ def run_find_coroutine():
|
515 | 548 |
|
516 | 549 | loop.set_alarm_in(0, start)
|
517 | 550 |
|
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 |
519 | 565 |
|
520 | 566 | if config.hist_length:
|
521 | 567 | histfilename = os.path.expanduser(config.hist_file)
|
|
0 commit comments