Skip to content

Commit a84abd2

Browse files
committed
aiorepl: Add tab completion support.
Use micropython.repl_autocomplete() to provide tab completion matching the native REPL behavior: single match inserts the completion, multiple matches prints candidates and redraws the prompt, tab after whitespace inserts 4 spaces for indentation. Falls back gracefully on ports without MICROPY_HELPER_REPL. Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
1 parent 6ae440a commit a84abd2

File tree

5 files changed

+80
-2
lines changed

5 files changed

+80
-2
lines changed

micropython/aiorepl/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Ctrl-D at the asyncio REPL command prompt will terminate the current event loop,
9494

9595
The following features are unsupported:
9696

97-
* Tab completion is not supported (also unsupported in `python -m asyncio`).
97+
* Tab completion requires `micropython.repl_autocomplete` (available when firmware is built with `MICROPY_HELPER_REPL`, which is the default for most ports).
9898
* Multi-line continuation. However you can do single-line definitions of functions, see demo above.
9999
* Exception tracebacks. Only the exception type and message is shown, see demo above.
100100
* Emacs shortcuts (e.g. Ctrl-A, Ctrl-E, to move to start/end of line).

micropython/aiorepl/aiorepl.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ async def task(g=None, prompt="--> "):
106106
hist_n = 0 # Number of history entries.
107107
c = 0 # ord of most recent character.
108108
t = 0 # timestamp of most recent character.
109+
_autocomplete = getattr(micropython, "repl_autocomplete", None)
109110
while True:
110111
hist_b = 0 # How far back in the history are we currently.
111112
sys.stdout.write(prompt)
@@ -187,6 +188,31 @@ async def task(g=None, prompt="--> "):
187188
elif c == CHAR_CTRL_E:
188189
sys.stdout.write("paste mode; Ctrl-C to cancel, Ctrl-D to finish\n===\n")
189190
paste = True
191+
elif c == 0x09 and not paste:
192+
# Tab key.
193+
cursor_pos = len(cmd) - curs
194+
if cursor_pos > 0 and cmd[cursor_pos - 1] <= " ":
195+
# Insert 4 spaces for indentation after whitespace.
196+
compl = " "
197+
elif _autocomplete and cursor_pos > 0:
198+
compl = _autocomplete(cmd[:cursor_pos])
199+
else:
200+
compl = ""
201+
if compl:
202+
# Insert completion at cursor.
203+
if curs:
204+
cmd = "".join((cmd[:-curs], compl, cmd[-curs:]))
205+
sys.stdout.write(cmd[-curs - len(compl) :])
206+
sys.stdout.write("\x1b[{}D".format(curs))
207+
else:
208+
sys.stdout.write(compl)
209+
cmd += compl
210+
elif compl is None:
211+
# Multiple matches printed by autocomplete, redraw line.
212+
sys.stdout.write(prompt)
213+
sys.stdout.write(cmd)
214+
if curs:
215+
sys.stdout.write("\x1b[{}D".format(curs))
190216
elif c == 0x1B:
191217
# Start of escape sequence.
192218
key = await s.read(2)

micropython/aiorepl/manifest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
metadata(
2-
version="0.2.2",
2+
version="0.3.0",
33
description="Provides an asynchronous REPL that can run concurrently with an asyncio, also allowing await expressions.",
44
)
55

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Test tab completion logic used by aiorepl.
2+
import sys
3+
import micropython
4+
5+
try:
6+
micropython.repl_autocomplete
7+
except AttributeError:
8+
print("SKIP")
9+
raise SystemExit
10+
11+
# Test the autocomplete API contract that aiorepl depends on.
12+
13+
# Single completion: keyword "import"
14+
result = micropython.repl_autocomplete("impo")
15+
print(repr(result))
16+
17+
# No match: returns empty string
18+
result = micropython.repl_autocomplete("xyz_no_match_zzz")
19+
print(repr(result))
20+
21+
# Multiple matches: returns None (candidates printed to stdout by C code).
22+
# Create two globals sharing a prefix so autocomplete finds multiple matches.
23+
import __main__
24+
25+
__main__.tvar_alpha = 1
26+
__main__.tvar_beta = 2
27+
result = micropython.repl_autocomplete("tvar_")
28+
del __main__.tvar_alpha
29+
del __main__.tvar_beta
30+
print("multiple:", repr(result))
31+
32+
# Test the whitespace-before-cursor logic used for tab-as-indentation.
33+
# This validates the condition: cursor_pos > 0 and cmd[cursor_pos - 1] <= " "
34+
test_cases = [
35+
("x ", True), # space before cursor
36+
("x", False), # non-whitespace before cursor
37+
("\n", True), # newline counts as whitespace
38+
("", False), # empty line (cursor_pos == 0)
39+
]
40+
for cmd, expected in test_cases:
41+
cursor_pos = len(cmd)
42+
is_whitespace = cursor_pos > 0 and cmd[cursor_pos - 1] <= " "
43+
print(cmd.encode(), is_whitespace == expected)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'rt '
2+
''
3+
4+
tvar_alpha tvar_beta
5+
multiple: None
6+
b'x ' True
7+
b'x' True
8+
b'\n' True
9+
b'' True

0 commit comments

Comments
 (0)