X Tutup
Skip to content

Commit fb4e0ec

Browse files
first pass at signal handling
--HG-- branch : scroll-frontend
1 parent eadd2e7 commit fb4e0ec

File tree

2 files changed

+58
-5
lines changed

2 files changed

+58
-5
lines changed

bpython/scrollfrontend/coderunner.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import threading
66
import logging
77

8+
class SigintHappened(object):
9+
pass
10+
811
class CodeRunner(object):
912
"""Runs user code in an interpreter, taking care of stdout/in/err"""
1013
def __init__(self, interp=None, stuff_a_refresh_request=lambda:None):
@@ -15,6 +18,8 @@ def __init__(self, interp=None, stuff_a_refresh_request=lambda:None):
1518
self.responses_for_code_thread = Queue.Queue(maxsize=1)
1619
self.stuff_a_refresh_request = stuff_a_refresh_request
1720
self.code_is_waiting = False
21+
self.sigint_happened = False
22+
self.orig_sigint_handler = None
1823

1924
@property
2025
def running(self):
@@ -42,24 +47,39 @@ def run_code(self, for_code=None):
4247
assert self.source is not None
4348
self.code_thread = threading.Thread(target=self._blocking_run_code, name='codethread')
4449
self.code_thread.daemon = True
50+
self.orig_sigint_handler = signal.getsignal(signal.SIGINT)
51+
signal.signal(signal.SIGINT, signal.default_int_handler)
4552
self.code_thread.start()
4653
else:
4754
assert self.code_is_waiting
4855
self.code_is_waiting = False
49-
self.responses_for_code_thread.put(for_code)
56+
signal.signal(signal.SIGINT, signal.default_int_handler)
57+
if self.sigint_happened:
58+
self.sigint_happened = False
59+
self.responses_for_code_thread.put(SigintHappened)
60+
else:
61+
self.responses_for_code_thread.put(for_code)
5062

5163
request = self.requests_from_code_thread.get()
64+
signal.signal(signal.SIGINT, self.sigint_handler_while_fufilling_code_request)
5265
if request in ['wait', 'refresh']:
5366
self.code_is_waiting = True
5467
if request == 'refresh':
5568
self.stuff_a_refresh_request()
5669
return False
5770
elif request in ['done', 'unfinished']:
5871
self._unload_code()
72+
if self.orig_sigint_handler:
73+
signal.signal(signal.SIGINT, self.orig_sigint_handler)
74+
self.orig_sigint_handler = None
5975
return request
6076
else:
6177
raise ValueError("Not a valid request_from_code_thread value: %r" % request)
6278

79+
def sigint_handler_while_fufilling_code_request(self, *args):
80+
logging.debug('while fufilling code request sigint handler running!')
81+
self.sigint_happened = True
82+
6383
def _blocking_run_code(self):
6484
unfinished = self.interp.runsource(self.source)
6585
self.requests_from_code_thread.put('unfinished' if unfinished else 'done')
@@ -70,12 +90,18 @@ def wait_and_get_value(self):
7090
Nothing means calls to run_code must be...
7191
"""
7292
self.requests_from_code_thread.put('wait')
73-
return self.responses_for_code_thread.get()
93+
value = self.responses_for_code_thread.get()
94+
if value is SigintHappened:
95+
raise KeyboardInterrupt()
96+
return value
7497

7598
def refresh_and_get_value(self):
7699
"""Returns the argument passed in to .run_code(for_code) """
77100
self.requests_from_code_thread.put('refresh')
78-
return self.responses_for_code_thread.get()
101+
value = self.responses_for_code_thread.get()
102+
if value is SigintHappened:
103+
raise KeyboardInterrupt()
104+
return value
79105

80106
class FakeOutput(object):
81107
def __init__(self, coderunner, please):
@@ -85,7 +111,7 @@ def write(self, *args, **kwargs):
85111
self.please(*args, **kwargs)
86112
return self.coderunner.refresh_and_get_value()
87113

88-
if __name__ == '__main__':
114+
def test_simple():
89115
orig_stdout = sys.stdout
90116
orig_stderr = sys.stderr
91117
c = CodeRunner(stuff_a_refresh_request=lambda: orig_stdout.flush() or orig_stderr.flush())
@@ -95,3 +121,18 @@ def write(self, *args, **kwargs):
95121
c.run_code()
96122
c.run_code()
97123
c.run_code()
124+
125+
def test_exception():
126+
orig_stdout = sys.stdout
127+
orig_stderr = sys.stderr
128+
c = CodeRunner(stuff_a_refresh_request=lambda: orig_stdout.flush() or orig_stderr.flush())
129+
def ctrlc():
130+
raise KeyboardInterrupt()
131+
stdout = FakeOutput(c, lambda x: ctrlc())
132+
sys.stdout = stdout
133+
c.load_code('1 + 1')
134+
c.run_code()
135+
136+
if __name__ == '__main__':
137+
test_simple()
138+

bpython/scrollfrontend/repl.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from fmtstr.fmtstr import fmtstr, FmtStr
2222
from fmtstr.bpythonparse import parse as bpythonparse
2323
from fmtstr.bpythonparse import func_for_letter, color_for_letter
24-
from fmtstr.events import pp_event
24+
from fmtstr.events import pp_event, SigIntEvent
2525

2626
from bpython.scrollfrontend.manual_readline import char_sequences as rl_char_sequences
2727
from bpython.scrollfrontend.manual_readline import get_updated_char_sequences
@@ -198,6 +198,10 @@ def process_event(self, e):
198198
return
199199
self.last_events.append(e)
200200
self.last_events.pop(0)
201+
if isinstance(e, events.SigIntEvent):
202+
logging.debug('received sigint event')
203+
self.keyboard_interrupt()
204+
return
201205
if isinstance(e, events.WindowChangeEvent):
202206
logging.debug('window change to %d %d', e.width, e.height)
203207
self.width, self.height = e.width, e.height
@@ -447,6 +451,14 @@ def run_code_and_maybe_finish(self, for_code=None):
447451
self.cursor_offset_in_line = len(self._current_line)
448452
self.done = not unfinished
449453

454+
def keyboard_interrupt(self):
455+
self.display_lines.extend(paint.display_linize(self.current_cursor_line, self.width))
456+
self.display_lines.extend(paint.display_linize("KeyboardInterrupt", self.width))
457+
self.saved_indent = 0
458+
self._current_line = ''
459+
self.cursor_offset_in_line = len(self._current_line)
460+
self.done = True
461+
450462
def unhighlight_paren(self):
451463
"""modify line in self.display_buffer to unhighlight a paren if possible
452464

0 commit comments

Comments
 (0)
X Tutup