From a84abd27909fd8aa06f05fbc19be879adf28b293 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: 2026年2月15日 14:02:15 +1100 Subject: [PATCH] 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 --- micropython/aiorepl/README.md | 2 +- micropython/aiorepl/aiorepl.py | 26 ++++++++++++ micropython/aiorepl/manifest.py | 2 +- micropython/aiorepl/test_autocomplete.py | 43 ++++++++++++++++++++ micropython/aiorepl/test_autocomplete.py.exp | 9 ++++ 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 micropython/aiorepl/test_autocomplete.py create mode 100644 micropython/aiorepl/test_autocomplete.py.exp diff --git a/micropython/aiorepl/README.md b/micropython/aiorepl/README.md index c1c08b899..d2e2c3b2b 100644 --- a/micropython/aiorepl/README.md +++ b/micropython/aiorepl/README.md @@ -94,7 +94,7 @@ Ctrl-D at the asyncio REPL command prompt will terminate the current event loop, The following features are unsupported: -* Tab completion is not supported (also unsupported in `python -m asyncio`). +* Tab completion requires `micropython.repl_autocomplete` (available when firmware is built with `MICROPY_HELPER_REPL`, which is the default for most ports). * Multi-line continuation. However you can do single-line definitions of functions, see demo above. * Exception tracebacks. Only the exception type and message is shown, see demo above. * Emacs shortcuts (e.g. Ctrl-A, Ctrl-E, to move to start/end of line). diff --git a/micropython/aiorepl/aiorepl.py b/micropython/aiorepl/aiorepl.py index 15026e435..7e985a54e 100644 --- a/micropython/aiorepl/aiorepl.py +++ b/micropython/aiorepl/aiorepl.py @@ -106,6 +106,7 @@ async def task(g=None, prompt="--> "): hist_n = 0 # Number of history entries. c = 0 # ord of most recent character. t = 0 # timestamp of most recent character. + _autocomplete = getattr(micropython, "repl_autocomplete", None) while True: hist_b = 0 # How far back in the history are we currently. sys.stdout.write(prompt) @@ -187,6 +188,31 @@ async def task(g=None, prompt="--> "): elif c == CHAR_CTRL_E: sys.stdout.write("paste mode; Ctrl-C to cancel, Ctrl-D to finish\n===\n") paste = True + elif c == 0x09 and not paste: + # Tab key. + cursor_pos = len(cmd) - curs + if cursor_pos> 0 and cmd[cursor_pos - 1] <= " ": + # Insert 4 spaces for indentation after whitespace. + compl = " " + elif _autocomplete and cursor_pos> 0: + compl = _autocomplete(cmd[:cursor_pos]) + else: + compl = "" + if compl: + # Insert completion at cursor. + if curs: + cmd = "".join((cmd[:-curs], compl, cmd[-curs:])) + sys.stdout.write(cmd[-curs - len(compl) :]) + sys.stdout.write("\x1b[{}D".format(curs)) + else: + sys.stdout.write(compl) + cmd += compl + elif compl is None: + # Multiple matches printed by autocomplete, redraw line. + sys.stdout.write(prompt) + sys.stdout.write(cmd) + if curs: + sys.stdout.write("\x1b[{}D".format(curs)) elif c == 0x1B: # Start of escape sequence. key = await s.read(2) diff --git a/micropython/aiorepl/manifest.py b/micropython/aiorepl/manifest.py index 83802e1c0..6def2aba9 100644 --- a/micropython/aiorepl/manifest.py +++ b/micropython/aiorepl/manifest.py @@ -1,5 +1,5 @@ metadata( - version="0.2.2", + version="0.3.0", description="Provides an asynchronous REPL that can run concurrently with an asyncio, also allowing await expressions.", ) diff --git a/micropython/aiorepl/test_autocomplete.py b/micropython/aiorepl/test_autocomplete.py new file mode 100644 index 000000000..0125b209b --- /dev/null +++ b/micropython/aiorepl/test_autocomplete.py @@ -0,0 +1,43 @@ +# Test tab completion logic used by aiorepl. +import sys +import micropython + +try: + micropython.repl_autocomplete +except AttributeError: + print("SKIP") + raise SystemExit + +# Test the autocomplete API contract that aiorepl depends on. + +# Single completion: keyword "import" +result = micropython.repl_autocomplete("impo") +print(repr(result)) + +# No match: returns empty string +result = micropython.repl_autocomplete("xyz_no_match_zzz") +print(repr(result)) + +# Multiple matches: returns None (candidates printed to stdout by C code). +# Create two globals sharing a prefix so autocomplete finds multiple matches. +import __main__ + +__main__.tvar_alpha = 1 +__main__.tvar_beta = 2 +result = micropython.repl_autocomplete("tvar_") +del __main__.tvar_alpha +del __main__.tvar_beta +print("multiple:", repr(result)) + +# Test the whitespace-before-cursor logic used for tab-as-indentation. +# This validates the condition: cursor_pos> 0 and cmd[cursor_pos - 1] <= " " +test_cases = [ + ("x ", True), # space before cursor + ("x", False), # non-whitespace before cursor + ("\n", True), # newline counts as whitespace + ("", False), # empty line (cursor_pos == 0) +] +for cmd, expected in test_cases: + cursor_pos = len(cmd) + is_whitespace = cursor_pos> 0 and cmd[cursor_pos - 1] <= " " + print(cmd.encode(), is_whitespace == expected) diff --git a/micropython/aiorepl/test_autocomplete.py.exp b/micropython/aiorepl/test_autocomplete.py.exp new file mode 100644 index 000000000..a83c93a1e --- /dev/null +++ b/micropython/aiorepl/test_autocomplete.py.exp @@ -0,0 +1,9 @@ +'rt ' +'' + +tvar_alpha tvar_beta +multiple: None +b'x ' True +b'x' True +b'\n' True +b'' True

AltStyle によって変換されたページ (->オリジナル) /