Skip to content

Commit e3dbb91

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

File tree

2 files changed

+35
-1
lines changed

2 files changed

+35
-1
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)

python/private/repl.bzl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""Implementation of the rules to expose a REPL."""
22

33
load("//python:py_binary.bzl", _py_binary = "py_binary")
4+
load("@bazel_skylib//lib:paths.bzl", "paths")
45

56
def _generate_repl_main_impl(ctx):
67
stub_repo = ctx.attr.stub.label.repo_name or ctx.workspace_name
7-
stub_path = "/".join([stub_repo, ctx.file.stub.short_path])
8+
unnormalized_path = "/".join([stub_repo, ctx.file.stub.short_path])
9+
stub_path = paths.normalize(unnormalized_path)
810

911
out = ctx.actions.declare_file(ctx.label.name + ".py")
1012

0 commit comments

Comments
 (0)