5
5
import re
6
6
import sys
7
7
import time
8
- import uasyncio as asyncio
8
+ import asyncio
9
9
10
10
# Import statement (needs to be global, and does not return).
11
11
_RE_IMPORT = re .compile ("^import ([^ ]+)( as ([^ ]+))?" )
19
19
_HISTORY_LIMIT = const (5 + 1 )
20
20
21
21
22
+ CHAR_CTRL_A = const (1 )
23
+ CHAR_CTRL_B = const (2 )
24
+ CHAR_CTRL_C = const (3 )
25
+ CHAR_CTRL_D = const (4 )
26
+ CHAR_CTRL_E = const (5 )
27
+
28
+
22
29
async def execute (code , g , s ):
23
30
if not code .strip ():
24
31
return
@@ -34,18 +41,16 @@ async def execute(code, g, s):
34
41
code = "return {}" .format (code )
35
42
36
43
code = """
37
- import uasyncio as asyncio
44
+ import asyncio
38
45
async def __code():
39
46
{}
40
47
41
48
__exec_task = asyncio.create_task(__code())
42
- """ .format (
43
- code
44
- )
49
+ """ .format (code )
45
50
46
51
async def kbd_intr_task (exec_task , s ):
47
52
while True :
48
- if ord (await s .read (1 )) == 0x03 :
53
+ if ord (await s .read (1 )) == CHAR_CTRL_C :
49
54
exec_task .cancel ()
50
55
return
51
56
@@ -104,7 +109,9 @@ async def task(g=None, prompt="--> "):
104
109
while True :
105
110
hist_b = 0 # How far back in the history are we currently.
106
111
sys .stdout .write (prompt )
107
- cmd = ""
112
+ cmd : str = ""
113
+ paste = False
114
+ curs = 0 # cursor offset from end of cmd buffer
108
115
while True :
109
116
b = await s .read (1 )
110
117
pc = c # save previous character
@@ -114,11 +121,19 @@ async def task(g=None, prompt="--> "):
114
121
if c < 0x20 or c > 0x7E :
115
122
if c == 0x0A :
116
123
# LF
124
+ if paste :
125
+ sys .stdout .write (b )
126
+ cmd += b
127
+ continue
117
128
# If the previous character was also LF, and was less
118
129
# than 20 ms ago, this was likely due to CRLF->LFLF
119
130
# conversion, so ignore this linefeed.
120
131
if pc == 0x0A and time .ticks_diff (t , pt ) < 20 :
121
132
continue
133
+ if curs :
134
+ # move cursor to end of the line
135
+ sys .stdout .write ("\x1B [{}C" .format (curs ))
136
+ curs = 0
122
137
sys .stdout .write ("\n " )
123
138
if cmd :
124
139
# Push current command.
@@ -135,31 +150,45 @@ async def task(g=None, prompt="--> "):
135
150
elif c == 0x08 or c == 0x7F :
136
151
# Backspace.
137
152
if cmd :
138
- cmd = cmd [:- 1 ]
139
- sys .stdout .write ("\x08 \x08 " )
140
- elif c == 0x02 :
141
- # Ctrl-B
153
+ if curs :
154
+ cmd = "" .join ((cmd [: - curs - 1 ], cmd [- curs :]))
155
+ sys .stdout .write (
156
+ "\x08 \x1B [K"
157
+ ) # 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
160
+ else :
161
+ cmd = cmd [:- 1 ]
162
+ sys .stdout .write ("\x08 \x08 " )
163
+ elif c == CHAR_CTRL_A :
164
+ await raw_repl (s , g )
165
+ break
166
+ elif c == CHAR_CTRL_B :
142
167
continue
143
- elif c == 0x03 :
144
- # Ctrl-C
145
- if pc == 0x03 and time .ticks_diff (t , pt ) < 20 :
146
- # Two very quick Ctrl-C (faster than a human
147
- # typing) likely means mpremote trying to
148
- # escape.
149
- asyncio .new_event_loop ()
150
- return
168
+ elif c == CHAR_CTRL_C :
169
+ if paste :
170
+ break
151
171
sys .stdout .write ("\n " )
152
172
break
153
- elif c == 0x04 :
154
- # Ctrl-D
173
+ elif c == CHAR_CTRL_D :
174
+ if paste :
175
+ result = await execute (cmd , g , s )
176
+ if result is not None :
177
+ sys .stdout .write (repr (result ))
178
+ sys .stdout .write ("\n " )
179
+ break
180
+
155
181
sys .stdout .write ("\n " )
156
182
# Shutdown asyncio.
157
183
asyncio .new_event_loop ()
158
184
return
185
+ elif c == CHAR_CTRL_E :
186
+ sys .stdout .write ("paste mode; Ctrl-C to cancel, Ctrl-D to finish\n ===\n " )
187
+ paste = True
159
188
elif c == 0x1B :
160
189
# Start of escape sequence.
161
190
key = await s .read (2 )
162
- if key in ("[A" , "[B" ):
191
+ if key in ("[A" , "[B" ): # up, down
163
192
# Stash the current command.
164
193
hist [(hist_i - hist_b ) % _HISTORY_LIMIT ] = cmd
165
194
# Clear current command.
@@ -175,12 +204,122 @@ async def task(g=None, prompt="--> "):
175
204
# Update current command.
176
205
cmd = hist [(hist_i - hist_b ) % _HISTORY_LIMIT ]
177
206
sys .stdout .write (cmd )
207
+ elif key == "[D" : # left
208
+ if curs < len (cmd ) - 1 :
209
+ curs += 1
210
+ sys .stdout .write ("\x1B " )
211
+ sys .stdout .write (key )
212
+ elif key == "[C" : # right
213
+ if curs :
214
+ curs -= 1
215
+ sys .stdout .write ("\x1B " )
216
+ sys .stdout .write (key )
217
+ elif key == "[H" : # home
218
+ pcurs = curs
219
+ curs = len (cmd )
220
+ sys .stdout .write ("\x1B [{}D" .format (curs - pcurs )) # move cursor left
221
+ elif key == "[F" : # end
222
+ pcurs = curs
223
+ curs = 0
224
+ sys .stdout .write ("\x1B [{}C" .format (pcurs )) # move cursor right
178
225
else :
179
226
# sys.stdout.write("\\x")
180
227
# sys.stdout.write(hex(c))
181
228
pass
182
229
else :
183
- sys .stdout .write (b )
184
- cmd += b
230
+ if curs :
231
+ # inserting into middle of line
232
+ 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
235
+ else :
236
+ sys .stdout .write (b )
237
+ cmd += b
185
238
finally :
186
239
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