6
6
import sys
7
7
import time
8
8
import asyncio
9
+ import io
10
+ import os
9
11
10
12
# Import statement (needs to be global, and does not return).
11
13
_RE_IMPORT = re .compile ("^import ([^ ]+)( as ([^ ]+))?" )
25
27
CHAR_CTRL_D = const (4 )
26
28
CHAR_CTRL_E = const (5 )
27
29
30
+ # Needed to enable terminal duplication with os.dupterm
31
+ class OutputStream (io .IOBase ):
32
+ def __init__ (self , stream ):
33
+ self .stream = stream
34
+
35
+ def write (self , data ):
36
+ self .stream .write (data )
37
+
38
+ def readinto (self , buf ):
39
+ return None
28
40
29
- async def execute (code , g , s ):
41
+
42
+ async def execute (code , g , in_stream , out_stream ):
30
43
if not code .strip ():
31
44
return
32
45
@@ -48,9 +61,9 @@ async def __code():
48
61
__exec_task = asyncio.create_task(__code())
49
62
""" .format (code )
50
63
51
- async def kbd_intr_task (exec_task , s ):
64
+ async def kbd_intr_task (exec_task , in_stream ):
52
65
while True :
53
- if ord (await s .read (1 )) == CHAR_CTRL_C :
66
+ if ord (await in_stream .read (1 )) == CHAR_CTRL_C :
54
67
exec_task .cancel ()
55
68
return
56
69
@@ -60,21 +73,31 @@ async def kbd_intr_task(exec_task, s):
60
73
61
74
# Concurrently wait for either Ctrl-C from the stream or task
62
75
# completion.
63
- intr_task = asyncio .create_task (kbd_intr_task (exec_task , s ))
76
+ intr_task = asyncio .create_task (kbd_intr_task (exec_task , in_stream ))
64
77
78
+ prev = None
79
+ if out_stream != sys .stdout :
80
+ prev = os .dupterm (out_stream )
81
+
65
82
try :
66
83
try :
67
84
return await exec_task
68
85
except asyncio .CancelledError :
69
86
pass
70
87
finally :
88
+ os .dupterm (prev )
89
+
71
90
intr_task .cancel ()
72
91
try :
73
92
await intr_task
74
93
except asyncio .CancelledError :
75
94
pass
76
95
else :
77
96
# Excute code snippet directly.
97
+ prev = None
98
+ if out_stream != sys .stdout :
99
+ prev = os .dupterm (out_stream )
100
+
78
101
try :
79
102
try :
80
103
micropython .kbd_intr (3 )
@@ -86,20 +109,31 @@ async def kbd_intr_task(exec_task, s):
86
109
except KeyboardInterrupt :
87
110
pass
88
111
finally :
112
+ os .dupterm (prev )
89
113
micropython .kbd_intr (- 1 )
90
114
91
115
except Exception as err :
92
116
print ("{}: {}" .format (type (err ).__name__ , err ))
93
117
94
118
95
119
# REPL task. Invoke this with an optional mutable globals dict.
96
- async def task (g = None , prompt = "--> " ):
120
+ # The in_stream should be an object that has an async read method
121
+ # The outstream should be an object that has a (non-async) write method
122
+ async def task (in_stream = None , out_stream = None , g = None , prompt = "--> " ):
97
123
print ("Starting asyncio REPL..." )
98
124
if g is None :
99
125
g = __import__ ("__main__" ).__dict__
126
+
127
+ if in_stream is None :
128
+ in_stream = asyncio .StreamReader (sys .stdin )
129
+
130
+ if out_stream is None :
131
+ out_stream = sys .stdout
132
+ else :
133
+ out_stream = OutputStream (out_stream )
134
+
100
135
try :
101
136
micropython .kbd_intr (- 1 )
102
- s = asyncio .StreamReader (sys .stdin )
103
137
# clear = True
104
138
hist = [None ] * _HISTORY_LIMIT
105
139
hist_i = 0 # Index of most recent entry.
@@ -108,12 +142,12 @@ async def task(g=None, prompt="--> "):
108
142
t = 0 # timestamp of most recent character.
109
143
while True :
110
144
hist_b = 0 # How far back in the history are we currently.
111
- sys . stdout .write (prompt )
145
+ out_stream .write (prompt )
112
146
cmd : str = ""
113
147
paste = False
114
148
curs = 0 # cursor offset from end of cmd buffer
115
149
while True :
116
- b = await s .read (1 )
150
+ b = await in_stream .read (1 )
117
151
pc = c # save previous character
118
152
c = ord (b )
119
153
pt = t # save previous time
@@ -122,7 +156,7 @@ async def task(g=None, prompt="--> "):
122
156
if c == 0x0A :
123
157
# LF
124
158
if paste :
125
- sys . stdout .write (b )
159
+ out_stream .write (b )
126
160
cmd += b
127
161
continue
128
162
# If the previous character was also LF, and was less
@@ -132,194 +166,107 @@ async def task(g=None, prompt="--> "):
132
166
continue
133
167
if curs :
134
168
# move cursor to end of the line
135
- sys . stdout .write ("\x1B [{}C" .format (curs ))
169
+ out_stream .write ("\x1B [{}C" .format (curs ))
136
170
curs = 0
137
- sys . stdout .write ("\n " )
171
+ out_stream .write ("\n " )
138
172
if cmd :
139
173
# Push current command.
140
174
hist [hist_i ] = cmd
141
175
# Increase history length if possible, and rotate ring forward.
142
176
hist_n = min (_HISTORY_LIMIT - 1 , hist_n + 1 )
143
177
hist_i = (hist_i + 1 ) % _HISTORY_LIMIT
144
178
145
- result = await execute (cmd , g , s )
179
+ result = await execute (cmd , g , in_stream , out_stream )
146
180
if result is not None :
147
- sys . stdout .write (repr (result ))
148
- sys . stdout .write ("\n " )
181
+ out_stream .write (repr (result ))
182
+ out_stream .write ("\n " )
149
183
break
150
184
elif c == 0x08 or c == 0x7F :
151
185
# Backspace.
152
186
if cmd :
153
187
if curs :
154
188
cmd = "" .join ((cmd [: - curs - 1 ], cmd [- curs :]))
155
- sys . stdout .write (
189
+ out_stream .write (
156
190
"\x08 \x1B [K"
157
191
) # move cursor back, erase to end of line
158
- sys . stdout .write (cmd [- curs :]) # redraw line
159
- sys . stdout .write ("\x1B [{}D" .format (curs )) # reset cursor location
192
+ out_stream .write (cmd [- curs :]) # redraw line
193
+ out_stream .write ("\x1B [{}D" .format (curs )) # reset cursor location
160
194
else :
161
195
cmd = cmd [:- 1 ]
162
- sys . stdout .write ("\x08 \x08 " )
196
+ out_stream .write ("\x08 \x08 " )
163
197
elif c == CHAR_CTRL_A :
164
- await raw_repl (s , g )
165
- break
198
+ continue
166
199
elif c == CHAR_CTRL_B :
167
200
continue
168
201
elif c == CHAR_CTRL_C :
169
202
if paste :
170
203
break
171
- sys . stdout .write ("\n " )
204
+ out_stream .write ("\n " )
172
205
break
173
206
elif c == CHAR_CTRL_D :
174
207
if paste :
175
- result = await execute (cmd , g , s )
208
+ result = await execute (cmd , g , in_stream , out_stream )
176
209
if result is not None :
177
- sys . stdout .write (repr (result ))
178
- sys . stdout .write ("\n " )
210
+ out_stream .write (repr (result ))
211
+ out_stream .write ("\n " )
179
212
break
180
213
181
- sys . stdout .write ("\n " )
214
+ out_stream .write ("\n " )
182
215
# Shutdown asyncio.
183
216
asyncio .new_event_loop ()
184
217
return
185
218
elif c == CHAR_CTRL_E :
186
- sys . stdout .write ("paste mode; Ctrl-C to cancel, Ctrl-D to finish\n ===\n " )
219
+ out_stream .write ("paste mode; Ctrl-C to cancel, Ctrl-D to finish\n ===\n " )
187
220
paste = True
188
221
elif c == 0x1B :
189
222
# Start of escape sequence.
190
- key = await s .read (2 )
223
+ key = await in_stream .read (2 )
191
224
if key in ("[A" , "[B" ): # up, down
192
225
# Stash the current command.
193
226
hist [(hist_i - hist_b ) % _HISTORY_LIMIT ] = cmd
194
227
# Clear current command.
195
228
b = "\x08 " * len (cmd )
196
- sys . stdout .write (b )
197
- sys . stdout .write (" " * len (cmd ))
198
- sys . stdout .write (b )
229
+ out_stream .write (b )
230
+ out_stream .write (" " * len (cmd ))
231
+ out_stream .write (b )
199
232
# Go backwards or forwards in the history.
200
233
if key == "[A" :
201
234
hist_b = min (hist_n , hist_b + 1 )
202
235
else :
203
236
hist_b = max (0 , hist_b - 1 )
204
237
# Update current command.
205
238
cmd = hist [(hist_i - hist_b ) % _HISTORY_LIMIT ]
206
- sys . stdout .write (cmd )
239
+ out_stream .write (cmd )
207
240
elif key == "[D" : # left
208
241
if curs < len (cmd ) - 1 :
209
242
curs += 1
210
- sys . stdout .write ("\x1B " )
211
- sys . stdout .write (key )
243
+ out_stream .write ("\x1B " )
244
+ out_stream .write (key )
212
245
elif key == "[C" : # right
213
246
if curs :
214
247
curs -= 1
215
- sys . stdout .write ("\x1B " )
216
- sys . stdout .write (key )
248
+ out_stream .write ("\x1B " )
249
+ out_stream .write (key )
217
250
elif key == "[H" : # home
218
251
pcurs = curs
219
252
curs = len (cmd )
220
- sys . stdout .write ("\x1B [{}D" .format (curs - pcurs )) # move cursor left
253
+ out_stream .write ("\x1B [{}D" .format (curs - pcurs )) # move cursor left
221
254
elif key == "[F" : # end
222
255
pcurs = curs
223
256
curs = 0
224
- sys . stdout .write ("\x1B [{}C" .format (pcurs )) # move cursor right
257
+ out_stream .write ("\x1B [{}C" .format (pcurs )) # move cursor right
225
258
else :
226
- # sys.stdout .write("\\x")
227
- # sys.stdout .write(hex(c))
259
+ # out_stream .write("\\x")
260
+ # out_stream .write(hex(c))
228
261
pass
229
262
else :
230
263
if curs :
231
264
# inserting into middle of line
232
265
cmd = "" .join ((cmd [:- curs ], b , cmd [- curs :]))
233
- sys . stdout .write (cmd [- curs - 1 :]) # redraw line to end
234
- sys . stdout .write ("\x1B [{}D" .format (curs )) # reset cursor location
266
+ out_stream .write (cmd [- curs - 1 :]) # redraw line to end
267
+ out_stream .write ("\x1B [{}D" .format (curs )) # reset cursor location
235
268
else :
236
- sys . stdout .write (b )
269
+ out_stream .write (b )
237
270
cmd += b
238
271
finally :
239
272
micropython .kbd_intr (3 )
240
-
241
-
242
- async def raw_paste (s , g , window = 512 ):
243
- sys .stdout .write ("R\x01 " ) # supported
244
- sys .stdout .write (bytearray ([window & 0xFF , window >> 8 , 0x01 ]).decode ())
245
- eof = False
246
- idx = 0
247
- buff = bytearray (window )
248
- file = b""
249
- while not eof :
250
- for idx in range (window ):
251
- b = await s .read (1 )
252
- c = ord (b )
253
- if c == CHAR_CTRL_C or c == CHAR_CTRL_D :
254
- # end of file
255
- sys .stdout .write (chr (CHAR_CTRL_D ))
256
- if c == CHAR_CTRL_C :
257
- raise KeyboardInterrupt
258
- file += buff [:idx ]
259
- eof = True
260
- break
261
- buff [idx ] = c
262
-
263
- if not eof :
264
- file += buff
265
- sys .stdout .write ("\x01 " ) # indicate window available to host
266
-
267
- return file
268
-
269
-
270
- async def raw_repl (s : asyncio .StreamReader , g : dict ):
271
- heading = "raw REPL; CTRL-B to exit\n "
272
- line = ""
273
- sys .stdout .write (heading )
274
-
275
- while True :
276
- line = ""
277
- sys .stdout .write (">" )
278
- while True :
279
- b = await s .read (1 )
280
- c = ord (b )
281
- if c == CHAR_CTRL_A :
282
- rline = line
283
- line = ""
284
-
285
- if len (rline ) == 2 and ord (rline [0 ]) == CHAR_CTRL_E :
286
- if rline [1 ] == "A" :
287
- line = await raw_paste (s , g )
288
- break
289
- else :
290
- # reset raw REPL
291
- sys .stdout .write (heading )
292
- sys .stdout .write (">" )
293
- continue
294
- elif c == CHAR_CTRL_B :
295
- # exit raw REPL
296
- sys .stdout .write ("\n " )
297
- return 0
298
- elif c == CHAR_CTRL_C :
299
- # clear line
300
- line = ""
301
- elif c == CHAR_CTRL_D :
302
- # entry finished
303
- # indicate reception of command
304
- sys .stdout .write ("OK" )
305
- break
306
- else :
307
- # let through any other raw 8-bit value
308
- line += b
309
-
310
- if len (line ) == 0 :
311
- # Normally used to trigger soft-reset but stay in raw mode.
312
- # Fake it for aiorepl / mpremote.
313
- sys .stdout .write ("Ignored: soft reboot\n " )
314
- sys .stdout .write (heading )
315
-
316
- try :
317
- result = exec (line , g )
318
- if result is not None :
319
- sys .stdout .write (repr (result ))
320
- sys .stdout .write (chr (CHAR_CTRL_D ))
321
- except Exception as ex :
322
- print (line )
323
- sys .stdout .write (chr (CHAR_CTRL_D ))
324
- sys .print_exception (ex , sys .stdout )
325
- sys .stdout .write (chr (CHAR_CTRL_D ))
0 commit comments