Skip to content

Commit 991fbab

Browse files
committed
fix: tab completion for REPL
fixes #3090
1 parent 5281261 commit 991fbab

File tree

1 file changed

+32
-0
lines changed

1 file changed

+32
-0
lines changed

python/bin/repl_stub.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,27 @@
1717
console_locals = globals().copy()
1818

1919
import code
20+
import readline
21+
import rlcompleter
2022
import sys
2123

24+
class DynamicCompleter(rlcompleter.Completer):
25+
"""
26+
A custom completer that dynamically updates its namespace to include new
27+
imports made within the interactive session.
28+
"""
29+
def __init__(self, namespace):
30+
# Store a reference to the namespace, not a copy, so that changes to the namespace are
31+
# reflected.
32+
self.namespace = namespace
33+
34+
def complete(self, text, state):
35+
# Update the completer's internal namespace with the current interactive session's locals
36+
# and globals. This is the key to making new imports discoverable.
37+
rlcompleter.Completer.__init__(self, self.namespace)
38+
return super().complete(text, state)
39+
40+
2241
if sys.stdin.isatty():
2342
# Use the default options.
2443
exitmsg = None
@@ -28,5 +47,18 @@
2847
sys.ps1 = ""
2948
sys.ps2 = ""
3049

50+
# Set up tab completion.
51+
completer = DynamicCompleter(console_locals)
52+
readline.set_completer(completer.complete)
53+
54+
# TODO(jpwoodbu): Use readline.backend instead of readline.__doc__ once we can depend on having
55+
# Python >=3.13.
56+
if 'libedit' in readline.__doc__: # type: ignore
57+
readline.parse_and_bind("bind ^I rl_complete")
58+
elif 'GNU readline' in readline.__doc__: # type: ignore
59+
readline.parse_and_bind("tab: complete")
60+
else:
61+
print('Could not enable tab completion!')
62+
3163
# We set the banner to an empty string because the repl_template.py file already prints the banner.
3264
code.interact(local=console_locals, banner="", exitmsg=exitmsg)

0 commit comments

Comments
 (0)