Skip to content

Commit 7008439

Browse files
Refactor reactor into curtsies Input object
1 parent 3ce0ad3 commit 7008439

File tree

7 files changed

+97
-74
lines changed

7 files changed

+97
-74
lines changed

bpython/curtsies.py

Lines changed: 13 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
from bpython import args as bpargs
1818
from bpython.translations import _
1919
from bpython.importcompletion import find_iterator
20+
from bpython.curtsiesfrontend import events as bpythonevents
21+
22+
logger = logging.getLogger(__name__)
23+
2024

2125
repl = None # global for `from bpython.curtsies import repl`
2226
#WARNING Will be a problem if more than one repl is ever instantiated this way
@@ -73,48 +77,9 @@ def mainloop(config, locals_, banner, interp=None, paste=None, interactive=True)
7377
hide_cursor=False,
7478
extra_bytes_callback=input_generator.unget_bytes) as window:
7579

76-
reload_requests = []
77-
def request_reload(desc):
78-
reload_requests.append(curtsies.events.ReloadEvent([desc]))
79-
refresh_requests = []
80-
def request_refresh(when='now'):
81-
refresh_requests.append(curtsies.events.RefreshRequestEvent(when=when))
82-
83-
def event_or_refresh(timeout=None):
84-
if timeout is None:
85-
timeout = 2**25 # a year
86-
while True:
87-
starttime = time.time()
88-
while True:
89-
t = time.time()
90-
refresh_requests.sort(key=lambda r: 0 if r.when == 'now' else r.when)
91-
if refresh_requests and (refresh_requests[0].when == 'now' or refresh_requests[-1].when < t):
92-
yield refresh_requests.pop(0)
93-
elif reload_requests:
94-
e = reload_requests.pop()
95-
yield e
96-
else:
97-
if refresh_requests:
98-
next_refresh = refresh_requests.pop(0)
99-
time_until_next_scheduled_event = max(0, next_refresh.when - t)
100-
else:
101-
next_refresh = None
102-
time_until_next_scheduled_event = 2**25
103-
104-
time_to_wait = min(time_until_next_scheduled_event, max(0, starttime + timeout - t))
105-
106-
e = input_generator.send(time_to_wait)
107-
108-
if next_refresh is not None:
109-
if e is None and time.time() > t + time_until_next_scheduled_event:
110-
yield next_refresh
111-
continue
112-
else:
113-
refresh_requests.insert(0, next_refresh)
114-
115-
if starttime + timeout < time.time() or e is not None:
116-
yield e
117-
break
80+
request_refresh = input_generator.event_trigger(bpythonevents.RefreshRequestEvent)
81+
schedule_refresh = input_generator.scheduled_event_trigger(bpythonevents.ScheduledRefreshRequestEvent)
82+
request_reload = input_generator.threadsafe_event_trigger(bpythonevents.ReloadEvent)
11883

11984
def on_suspend():
12085
window.__exit__(None, None, None)
@@ -128,6 +93,7 @@ def after_suspend():
12893
with Repl(config=config,
12994
locals_=locals_,
13095
request_refresh=request_refresh,
96+
schedule_refresh=schedule_refresh,
13197
request_reload=request_reload,
13298
get_term_hw=window.get_term_hw,
13399
get_cursor_vertical_diff=window.get_cursor_vertical_diff,
@@ -157,11 +123,13 @@ def process_event(e):
157123
if paste:
158124
process_event(paste)
159125

160-
process_event(None) #priming the pump (do a display before waiting for first event)
161-
for _, e in izip(find_iterator, event_or_refresh(0)):
126+
process_event(None) # do a display before waiting for first event
127+
for _ in find_iterator:
128+
e = input_generator.send(0)
162129
if e is not None:
163130
process_event(e)
164-
for e in event_or_refresh(None):
131+
132+
for e in input_generator:
165133
process_event(e)
166134

167135
if __name__ == '__main__':

bpython/curtsiesfrontend/coderunner.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,19 @@ class CodeRunner(object):
6868
just passes whatever is passed in to run_code(for_code) to the
6969
code greenlet
7070
"""
71-
def __init__(self, interp=None, stuff_a_refresh_request=lambda:None):
71+
def __init__(self, interp=None, request_refresh=lambda:None):
7272
"""
7373
interp is an interpreter object to use. By default a new one is
7474
created.
7575
76-
stuff_a_refresh_request is a function that will be called each time
76+
request_refresh is a function that will be called each time
7777
the running code asks for a refresh - to, for example, update the screen.
7878
"""
7979
self.interp = interp or code.InteractiveInterpreter()
8080
self.source = None
8181
self.main_greenlet = greenlet.getcurrent()
8282
self.code_greenlet = None
83-
self.stuff_a_refresh_request = stuff_a_refresh_request
83+
self.request_refresh = request_refresh
8484
self.code_is_waiting = False # waiting for response from main thread
8585
self.sigint_happened_in_main_greenlet = False # sigint happened while in main thread
8686
self.orig_sigint_handler = None
@@ -125,12 +125,13 @@ def run_code(self, for_code=None):
125125
else:
126126
request = self.code_greenlet.switch(for_code)
127127

128+
logger.debug('request received from code was %r', request)
128129
if not issubclass(request, RequestFromCodeGreenlet):
129130
raise ValueError("Not a valid value from code greenlet: %r" % request)
130131
if request in [Wait, Refresh]:
131132
self.code_is_waiting = True
132133
if request == Refresh:
133-
self.stuff_a_refresh_request()
134+
self.request_refresh()
134135
return False
135136
elif request in [Done, Unfinished]:
136137
self._unload_code()
@@ -188,7 +189,7 @@ def isatty(self):
188189
def test_simple():
189190
orig_stdout = sys.stdout
190191
orig_stderr = sys.stderr
191-
c = CodeRunner(stuff_a_refresh_request=lambda: orig_stdout.flush() or orig_stderr.flush())
192+
c = CodeRunner(request_refresh=lambda: orig_stdout.flush() or orig_stderr.flush())
192193
stdout = FakeOutput(c, orig_stdout.write)
193194
sys.stdout = stdout
194195
c.load_code('1 + 1')
@@ -199,7 +200,7 @@ def test_simple():
199200
def test_exception():
200201
orig_stdout = sys.stdout
201202
orig_stderr = sys.stderr
202-
c = CodeRunner(stuff_a_refresh_request=lambda: orig_stdout.flush() or orig_stderr.flush())
203+
c = CodeRunner(request_refresh=lambda: orig_stdout.flush() or orig_stderr.flush())
203204
def ctrlc():
204205
raise KeyboardInterrupt()
205206
stdout = FakeOutput(c, lambda x: ctrlc())

bpython/curtsiesfrontend/events.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Non-keybaord events used in bpython curtsies REPL"""
2+
import time
3+
4+
import curtsies.events
5+
6+
7+
class ReloadEvent(curtsies.events.Event):
8+
"""Request to rerun REPL session ASAP because imported modules changed"""
9+
def __init__(self, files_modified=('?',)):
10+
self.files_modified = files_modified
11+
12+
def __repr__(self):
13+
return "<ReloadEvent from %s>" % (' & '.join(self.files_modified))
14+
15+
16+
class RefreshRequestEvent(curtsies.events.Event):
17+
"""Request to refresh REPL display ASAP"""
18+
def __init__(self, who='?'):
19+
self.who = who
20+
21+
def __repr__(self):
22+
return "<RefreshRequestEvent from %r for now>" % (self.who,)
23+
24+
25+
class ScheduledRefreshRequestEvent(curtsies.events.ScheduledEvent):
26+
"""Request to refresh the REPL display at some point in the future
27+
28+
Used to schedule the dissapearance of status bar message that only show
29+
for a few seconds"""
30+
def __init__(self, when, who='?'):
31+
self.who = who
32+
self.when = when # time.time() + how long
33+
34+
def __repr__(self):
35+
return ("<RefreshRequestEvent from %r for %s seconds from now>" %
36+
(self.who, self.when - time.time()))

bpython/curtsiesfrontend/filewatch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def on_any_event(self, event):
6363
dirpath = os.path.dirname(event.src_path)
6464
paths = [path + '.py' for path in self.dirs[dirpath]]
6565
if event.src_path in paths:
66-
self.on_change(event.src_path)
66+
self.on_change(files_modified=[event.src_path])
6767

6868
if __name__ == '__main__':
6969
m = ModuleChangedEventHandler([])

bpython/curtsiesfrontend/interaction.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ class StatusBar(BpythonInteraction):
1919
functionality in a evented or callback style, but trying to integrate
2020
bpython.Repl code.
2121
"""
22-
def __init__(self, permanent_text="", refresh_request=lambda: None):
22+
def __init__(self,
23+
permanent_text="",
24+
request_refresh=lambda: None,
25+
schedule_refresh=lambda when: None):
2326
self._current_line = ''
2427
self.cursor_offset_in_line = 0
2528
self.in_prompt = False
@@ -34,7 +37,8 @@ def __init__(self, permanent_text="", refresh_request=lambda: None):
3437
self.permanent_stack.append(permanent_text)
3538
self.main_greenlet = greenlet.getcurrent()
3639
self.request_greenlet = None
37-
self.refresh_request = refresh_request
40+
self.request_refresh = request_refresh
41+
self.schedule_refresh = schedule_refresh
3842

3943
def push_permanent_message(self, msg):
4044
self._message = ''
@@ -54,7 +58,7 @@ def message(self, msg):
5458
"""Sets a temporary message"""
5559
self.message_start_time = time.time()
5660
self._message = msg
57-
self.refresh_request(time.time() + self.message_time)
61+
self.schedule_refresh(time.time() + self.message_time)
5862

5963
def _check_for_expired_message(self):
6064
if self._message and time.time() > self.message_start_time + self.message_time:
@@ -129,7 +133,7 @@ def notify(self, msg, n=3):
129133
self.message_time = n
130134
self.message(msg)
131135
self.waiting_for_refresh = True
132-
self.refresh_request()
136+
self.request_refresh()
133137
self.main_greenlet.switch(msg)
134138

135139
# below Really ought to be called from greenlets other than main because they block

bpython/curtsiesfrontend/repl.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler
4444
from bpython.curtsiesfrontend.interaction import StatusBar
4545
from bpython.curtsiesfrontend.manual_readline import edit_keys
46+
from bpython.curtsiesfrontend import events as bpythonevents
4647

4748
#TODO other autocomplete modes (also fix in other bpython implementations)
4849

@@ -213,8 +214,10 @@ class Repl(BpythonRepl):
213214
def __init__(self,
214215
locals_=None,
215216
config=None,
216-
request_refresh=lambda when='now': None,
217-
request_reload=lambda desc: None, get_term_hw=lambda:(50, 10),
217+
request_refresh=lambda: None,
218+
schedule_refresh=lambda when=0: None,
219+
request_reload=lambda desc: None,
220+
get_term_hw=lambda:(50, 10),
218221
get_cursor_vertical_diff=lambda: 0,
219222
banner=None,
220223
interp=None,
@@ -259,22 +262,30 @@ def __init__(self,
259262

260263
self.reevaluating = False
261264
self.fake_refresh_requested = False
262-
def smarter_request_refresh(when='now'):
265+
def smarter_request_refresh():
263266
if self.reevaluating or self.paste_mode:
264267
self.fake_refresh_requested = True
265268
else:
266-
request_refresh(when=when)
269+
request_refresh()
267270
self.request_refresh = smarter_request_refresh
268-
def smarter_request_reload(desc):
271+
def smarter_schedule_refresh(when='now'):
272+
if self.reevaluating or self.paste_mode:
273+
self.fake_refresh_requested = True
274+
else:
275+
schedule_refresh(when=when)
276+
self.schedule_refresh = smarter_schedule_refresh
277+
def smarter_request_reload(files_modified=()):
269278
if self.watching_files:
270-
request_reload(desc)
279+
request_reload(files_modified=files_modified)
271280
else:
272281
pass
273282
self.request_reload = smarter_request_reload
274283
self.get_term_hw = get_term_hw
275284
self.get_cursor_vertical_diff = get_cursor_vertical_diff
276285

277-
self.status_bar = StatusBar('', refresh_request=self.request_refresh)
286+
self.status_bar = StatusBar('',
287+
request_refresh=self.request_refresh,
288+
schedule_refresh=self.schedule_refresh)
278289
self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch)
279290
logger.debug("starting parent init")
280291
super(Repl, self).__init__(interp, config)
@@ -423,18 +434,20 @@ def process_event(self, e):
423434

424435
logger.debug("processing event %r", e)
425436
if isinstance(e, events.Event):
426-
return self.proccess_control_event(e)
437+
return self.process_control_event(e)
427438
else:
428439
self.last_events.append(e)
429440
self.last_events.pop(0)
430441
return self.process_key_event(e)
431442

432-
def proccess_control_event(self, e):
443+
def process_control_event(self, e):
433444

434-
if isinstance(e, events.RefreshRequestEvent):
435-
if e.when != 'now':
436-
pass # This is a scheduled refresh - it's really just a refresh (so nop)
437-
elif self.status_bar.has_focus:
445+
if isinstance(e, bpythonevents.ScheduledRefreshRequestEvent):
446+
pass # This is a scheduled refresh - it's really just a refresh (so nop)
447+
448+
elif isinstance(e, bpythonevents.RefreshRequestEvent):
449+
logger.info('received ASAP refresh request event')
450+
if self.status_bar.has_focus:
438451
self.status_bar.process_event(e)
439452
else:
440453
assert self.coderunner.code_is_waiting
@@ -463,7 +476,7 @@ def proccess_control_event(self, e):
463476
self.keyboard_interrupt()
464477
return
465478

466-
elif isinstance(e, events.ReloadEvent):
479+
elif isinstance(e, bpythonevents.ReloadEvent):
467480
if self.watching_files:
468481
self.clear_modules_and_reevaluate()
469482
self.status_bar.message('Reloaded at ' + time.strftime('%H:%M:%S') + ' because ' + ' & '.join(e.files_modified) + ' modified')
@@ -1296,7 +1309,7 @@ def reevaluate(self, insert_into_history=False):
12961309
self.on_enter(insert_into_history=insert_into_history)
12971310
while self.fake_refresh_requested:
12981311
self.fake_refresh_requested = False
1299-
self.process_event(events.RefreshRequestEvent())
1312+
self.process_event(bpythonevents.RefreshRequestEvent())
13001313
sys.stdin = self.stdin
13011314
self.reevaluating = False
13021315

@@ -1405,6 +1418,7 @@ def compress_paste_event(paste_event):
14051418
else:
14061419
return None
14071420

1421+
#TODO this needs some work to function again and be useful for embedding
14081422
def simple_repl():
14091423
refreshes = []
14101424
def request_refresh():

bpython/test/test_curtsies_painting.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def skip(f):
1111

1212
from curtsies.formatstringarray import FormatStringTest, fsarray
1313
from curtsies.fmtfuncs import *
14-
from curtsies.events import RefreshRequestEvent
14+
from bpython.curtsiesfrontend.events import RefreshRequestEvent
1515

1616
from bpython import config
1717
from bpython.curtsiesfrontend.repl import Repl
@@ -89,8 +89,8 @@ def output_to_repl(repl):
8989
sys.stdout, sys.stderr = old_out, old_err
9090

9191
class TestCurtsiesRewindRedraw(TestCurtsiesPainting):
92-
def refresh(self, when='now'):
93-
self.refresh_requests.append(RefreshRequestEvent(when=when))
92+
def refresh(self):
93+
self.refresh_requests.append(RefreshRequestEvent())
9494

9595
def send_refreshes(self):
9696
while self.refresh_requests:

0 commit comments

Comments
 (0)