Skip to content

Commit ef1fce0

Browse files
edvilmeblurb-it[bot]brettcannon
authored
gh-157928: Have PyREPL detect OSC sequences (GH-157932)
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Brett Cannon <brett@python.org>
1 parent 7515b64 commit ef1fce0

4 files changed

Lines changed: 33 additions & 1 deletion

File tree

‎Lib/_pyrepl/utils.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,21 @@
1919
from .trace import trace
2020

2121

22-
ANSI_ESCAPE_SEQUENCE = re.compile(r"\x1b\[[ -@]*[A-~]")
22+
ANSI_ESCAPE_SEQUENCE = re.compile(
23+
r"""
24+
\x1b
25+
(?:
26+
# CSI: ESC [ followed by parameter/intermediate bytes and a final byte.
27+
# For example, ESC [ 31 m ("\x1b[31m") selects red foreground text.
28+
\[ [ -@]* [A-~]
29+
|
30+
# OSC: ESC ] followed by a payload terminated by BEL or ST (ESC \).
31+
# For example, ESC ] 0 ; title BEL ("\x1b]0;title\x07") sets the title.
32+
\] [^\x1b\x07]* (?: \x07 | \x1b\\ )
33+
)
34+
""",
35+
re.X,
36+
)
2337
ZERO_WIDTH_BRACKET = re.compile(r"\x01.*?\x02")
2438
ZERO_WIDTH_TRANS = str.maketrans({"\x01": "", "\x02": ""})
2539
IDENTIFIERS_AFTER = frozenset({"def", "class"})

‎Lib/test/test_pyrepl/test_render.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ def test_from_rendered_text_empty_string(self):
5555
self.assertEqual(line.text, "")
5656
self.assertEqual(line.width, 0)
5757

58+
def test_from_rendered_text_with_osc_control(self):
59+
osc = "\x1b]633;A\x1b\\"
60+
prompt = ">>> "
61+
line = RenderLine.from_rendered_text(osc + prompt)
62+
63+
self.assertEqual(line.width, len(prompt))
64+
self.assertEqual(line.cells[0].controls, (osc,))
65+
self.assertEqual(line.cells[0].text, "")
66+
5867
def test_from_rendered_text_with_non_sgr_controls(self):
5968
# \x1b[H is a cursor-home control (not SGR since it doesn't end with 'm')
6069
line = RenderLine.from_rendered_text("\x1b[Hx")

‎Lib/test/test_pyrepl/test_utils.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ def test_wlen(self):
4747
self.assertEqual(wlen('e\N{COMBINING ACUTE ACCENT}'), 1)
4848
self.assertEqual(wlen('a\N{ZERO WIDTH JOINER}b'), 2)
4949

50+
def test_wlen_with_osc_sequence(self):
51+
prompt = ">>> "
52+
self.assertEqual(wlen("\x1b]633;A\x07" + prompt), len(prompt))
53+
54+
def test_wlen_with_unterminated_osc_sequence_as_individual_characters(self):
55+
# \x1b is treated as a single character (ESC)
56+
self.assertEqual(wlen("\x1b]633;A"), len("\x1b]633;A"))
57+
5058
def test_prev_next_window(self):
5159
def gen_normal():
5260
yield 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Correctly detect and handle OSC sequences in PyREPL.

0 commit comments

Comments
 (0)