44import logging
55import code
66import threading
7+ import Queue
78from cStringIO import StringIO
9+ import traceback
810
911from bpython .autocomplete import Autocomplete , SIMPLE
1012from bpython .repl import Repl as BpythonRepl
2123from fmtstr .bpythonparse import parse as bpythonparse
2224from fmtstr .bpythonparse import func_for_letter
2325
26+ from bpython .scrollfrontend .manual_readline import char_sequences as rl_char_sequences
2427from bpython .scrollfrontend .manual_readline import get_updated_char_sequences
2528from bpython .scrollfrontend .abbreviate import substitute_abbreviations
2629from bpython .scrollfrontend .interaction import StatusBar
4649
4750from bpython .keys import cli_key_dispatch as key_dispatch
4851
52+ class FakeStdin (object ):
53+ def __init__ (self , on_receive_tuple ):
54+ self .request_from_executing_code = Queue .Queue (maxsize = 1 )
55+ self .response_queue = Queue .Queue (maxsize = 1 )
56+ self .has_focus = False
57+ self .current_line = ''
58+ self .cursor_offset_in_line = 0
59+ self .on_receive_tuple = on_receive_tuple
60+
61+ def wait_for_request_or_finish (self ):
62+ logging .debug ('waiting for message from running code (stack depth %r)' , len (traceback .format_stack ()))
63+ msg = self .request_from_executing_code .get (timeout = 3 )
64+ logging .debug ('FakeStdin received message %r' , msg )
65+ self .process_request (msg )
66+
67+ def process_request (self , msg ):
68+ if msg == 'can haz stdin line plz' :
69+ logging .debug ('setting focus to stdin' )
70+ self .has_focus = True
71+ elif isinstance (msg , tuple ):
72+ logging .debug ('calling on_finish_running_code' )
73+ self .on_receive_tuple (* msg )
74+ else :
75+ logging .debug ('unrecognized message!' )
76+ assert False
77+
78+ def process_event (self , e ):
79+ assert self .has_focus
80+ if e in rl_char_sequences :
81+ self .cursor_offset_in_line , self .current_line = rl_char_sequences [e ](self .cursor_offset_in_line , self ._current_line )
82+ #TODO EOF on ctrl-d
83+ else : # add normal character
84+ logging .debug ('adding normal char %r to current line' , e )
85+ self .current_line = (self .current_line [:self .cursor_offset_in_line ] +
86+ e +
87+ self .current_line [self .cursor_offset_in_line :])
88+ self .cursor_offset_in_line += 1
89+
90+ if self .current_line .endswith (("\n " , "\r " )):
91+ self .has_focus = False
92+ self .response_queue .put (self .current_line )
93+ self .current_line = ''
94+ self .cursor_offset_in_line = 0
95+ self .wait_for_request_or_finish ()
96+
97+ def readline (self ):
98+ self .request_from_executing_code .put ('can haz stdin line plz' )
99+ return self .response_queue .get (timeout = 15 )
100+
101+
49102class Repl (BpythonRepl ):
50103 """
51104
@@ -100,6 +153,8 @@ def __init__(self):
100153 self .cursor_offset_in_line = 0 # from the left, 0 means first char
101154 self .done = True
102155
156+ self .stdin = FakeStdin (self .on_finish_running_code )
157+
103158 self .paste_mode = False
104159
105160 self .width = None # will both be set by a window resize event
@@ -109,8 +164,10 @@ def __init__(self):
109164 def __enter__ (self ):
110165 self .orig_stdout = sys .stdout
111166 self .orig_stderr = sys .stderr
167+ self .orig_stdin = sys .stdin
112168 sys .stdout = StringIO ()
113169 sys .stderr = StringIO ()
170+ sys .stdin = self .stdin
114171 return self
115172
116173 def __exit__ (self , * args ):
@@ -147,6 +204,8 @@ def process_event(self, e):
147204 return
148205 if self .status_bar .has_focus :
149206 return self .status_bar .process_event (e )
207+ if self .stdin .has_focus :
208+ return self .stdin .process_event (e )
150209
151210 if e in self .rl_char_sequences :
152211 self .cursor_offset_in_line , self ._current_line = self .rl_char_sequences [e ](self .cursor_offset_in_line , self ._current_line )
@@ -224,15 +283,18 @@ def on_enter(self):
224283 self .rl_history .append (self ._current_line )
225284 self .rl_history .last ()
226285 self .history .append (self ._current_line )
227- output , err , self .done , indent = self .push (self ._current_line )
286+ self .push (self ._current_line )
287+
288+ def on_finish_running_code (self , output , error , done , indent ):
228289 if output :
229290 self .display_lines .extend (sum ([paint .display_linize (line , self .width ) for line in output .split ('\n ' )], []))
230- if err :
291+ if error :
231292 self .display_lines .extend ([func_for_letter (self .config .color_scheme ['error' ])(line )
232293 for line in sum ([paint .display_linize (line , self .width )
233- for line in err .split ('\n ' )], [])])
294+ for line in error .split ('\n ' )], [])])
234295 self ._current_line = ' ' * indent
235296 self .cursor_offset_in_line = len (self ._current_line )
297+ self .done = done
236298
237299 def on_tab (self , back = False ):
238300 """Do something on tab key
@@ -314,7 +376,21 @@ def push(self, line):
314376 """Push a line of code onto the buffer, run the buffer
315377
316378 If the interpreter successfully runs the code, clear the buffer
317- Return ("for stdout", "for_stderr", finished?)
379+ """
380+ t = threading .Thread (target = self .runsource , args = (line ,))
381+ t .daemon = True
382+ t .start ()
383+ logging .debug ('push() is now waiting' )
384+ self .stdin .wait_for_request_or_finish ()
385+ logging .debug ('push() done waiting' )
386+
387+ def runsource (self , line ):
388+ """Push a line of code on to the buffer, run the buffer, clean up
389+
390+ Makes requests for input and announces being done as necessary via threadsafe queue
391+ sends messages:
392+ * request for readline
393+ * (stdoutput, error, done?, amount_to_indent_next_line)
318394 """
319395 self .buffer .append (line )
320396 indent = len (re .match (r'[ ]*' , line ).group ())
@@ -327,7 +403,7 @@ def push(self, line):
327403 indent = max (0 , indent - self .config .tab_length )
328404 out_spot = sys .stdout .tell ()
329405 err_spot = sys .stderr .tell ()
330- # logging.debug('running %r in interpreter', self.buffer)
406+ logging .debug ('running %r in interpreter' , self .buffer )
331407 unfinished = self .interp .runsource ('\n ' .join (self .buffer ))
332408
333409 #current line not added to display buffer if quitting
@@ -349,15 +425,17 @@ def push(self, line):
349425
350426 if unfinished and not err :
351427 logging .debug ('unfinished - line added to buffer' )
352- return ( None , None , False , indent )
428+ self . stdin . request_from_executing_code . put (( None , None , False , indent ) )
353429 else :
354430 logging .debug ('finished - buffer cleared' )
355431 self .display_lines .extend (self .display_buffer_lines )
356432 self .display_buffer = []
357433 self .buffer = []
358434 if err :
359435 indent = 0
360- return (out [:- 1 ], err [:- 1 ], True , indent )
436+ logging .debug ('sending output info' )
437+ self .stdin .request_from_executing_code .put ((out [:- 1 ], err [:- 1 ], True , indent ))
438+ logging .debug ('sent output info' )
361439
362440 def unhighlight_paren (self ):
363441 """modify line in self.display_buffer to unhighlight a paren if possible"""
@@ -380,7 +458,9 @@ def current_line_formatted(self):
380458 fs = bpythonparse (format (self .tokenize (self ._current_line ), self .formatter ))
381459 else :
382460 fs = fmtstr (self ._current_line )
383- logging .debug ('calculating current formatted line: %r' , repr (fs ))
461+ if hasattr (self , 'old_fs' ) and str (fs ) != str (self .old_fs ):
462+ logging .debug ('calculating current formatted line: %r' , repr (fs ))
463+ self .old_fs = fs
384464 return fs
385465
386466 @property
@@ -482,6 +562,7 @@ def paint(self, about_to_exit=False):
482562 cursor_column = (self .cursor_offset_in_line + len (self .display_line_with_prompt ) - len (self ._current_line )) % width
483563
484564 if self .list_win_visible :
565+ #TODO infobox not properly expanding window! try reduce( docs about halfway down a 80x24 terminal
485566 logging .debug ('infobox display code running' )
486567 visible_space_above = history .height
487568 visible_space_below = min_height - cursor_row
0 commit comments