Skip to content

Commit 2fc5f1c

Browse files
jpwoodbuaignas
andauthored
feat(repl): add tab completion on platforms with readline support (#3114)
This adds tab completion to the default stub when using the REPL feature. However, the feature only works in environments with `readline` support, which means that with the bundled toolchains, Windows will not have tab completion. Work towards #3090 --------- Co-authored-by: Ignas Anikevicius <[email protected]>
1 parent 39703fa commit 2fc5f1c

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ END_UNRELEASED_TEMPLATE
104104

105105
{#v0-0-0-added}
106106
### Added
107+
* (repl) Default stub now has tab completion, where `readline` support is available,
108+
see ([#3114](https://github.com/bazel-contrib/rules_python/pull/3114)).
109+
([#3114](https://github.com/bazel-contrib/rules_python/pull/3114)).
107110
* (pypi) To configure the environment for `requirements.txt` evaluation, use the newly added
108111
developer preview of the `pip.default` tag class. Only `rules_python` and root modules can use
109112
this feature. You can also configure custom `config_settings` using `pip.default`.

python/bin/repl_stub.py

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

1919
import code
20+
import rlcompleter
2021
import sys
2122

23+
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+
30+
def __init__(self, namespace):
31+
# Store a reference to the namespace, not a copy, so that changes to the namespace are
32+
# reflected.
33+
self.namespace = namespace
34+
35+
def complete(self, text, state):
36+
# Update the completer's internal namespace with the current interactive session's locals
37+
# and globals. This is the key to making new imports discoverable.
38+
rlcompleter.Completer.__init__(self, self.namespace)
39+
return super().complete(text, state)
40+
41+
2242
if sys.stdin.isatty():
2343
# Use the default options.
2444
exitmsg = None
@@ -28,5 +48,29 @@
2848
sys.ps1 = ""
2949
sys.ps2 = ""
3050

51+
# Set up tab completion.
52+
try:
53+
import readline
54+
55+
completer = DynamicCompleter(console_locals)
56+
readline.set_completer(completer.complete)
57+
58+
# TODO(jpwoodbu): Use readline.backend instead of readline.__doc__ once we can depend on having
59+
# Python >=3.13.
60+
if "libedit" in readline.__doc__: # type: ignore
61+
readline.parse_and_bind("bind ^I rl_complete")
62+
elif "GNU readline" in readline.__doc__: # type: ignore
63+
readline.parse_and_bind("tab: complete")
64+
else:
65+
print(
66+
"Could not enable tab completion: "
67+
"unable to determine readline backend"
68+
)
69+
except ImportError:
70+
print(
71+
"Could not enable tab completion: "
72+
"readline module not available on this platform"
73+
)
74+
3175
# We set the banner to an empty string because the repl_template.py file already prints the banner.
3276
code.interact(local=console_locals, banner="", exitmsg=exitmsg)

0 commit comments

Comments
 (0)