Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion Lib/_pyrepl/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,21 @@
from .trace import trace


ANSI_ESCAPE_SEQUENCE = re.compile(r"\x1b\[[ -@]*[A-~]")
ANSI_ESCAPE_SEQUENCE = re.compile(
r"""
\x1b
(?:
# CSI: ESC [ followed by parameter/intermediate bytes and a final byte.
# For example, ESC [ 31 m ("\x1b[31m") selects red foreground text.
\[ [ -@]* [A-~]
|
# OSC: ESC ] followed by a payload terminated by BEL or ST (ESC \).
# For example, ESC ] 0 ; title BEL ("\x1b]0;title\x07") sets the title.
\] [^\x1b\x07]* (?: \x07 | \x1b\\ )
)
""",
re.X,
)
ZERO_WIDTH_BRACKET = re.compile(r"\x01.*?\x02")
ZERO_WIDTH_TRANS = str.maketrans({"\x01": "", "\x02": ""})
IDENTIFIERS_AFTER = frozenset({"def", "class"})
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_pyrepl/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ def test_from_rendered_text_empty_string(self):
self.assertEqual(line.text, "")
self.assertEqual(line.width, 0)

def test_from_rendered_text_with_osc_control(self):
osc = "\x1b]633;A\x1b\\"
prompt = ">>> "
line = RenderLine.from_rendered_text(osc + prompt)

self.assertEqual(line.width, len(prompt))
self.assertEqual(line.cells[0].controls, (osc,))
self.assertEqual(line.cells[0].text, "")

def test_from_rendered_text_with_non_sgr_controls(self):
# \x1b[H is a cursor-home control (not SGR since it doesn't end with 'm')
line = RenderLine.from_rendered_text("\x1b[Hx")
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_pyrepl/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ def test_wlen(self):
self.assertEqual(wlen('e\N{COMBINING ACUTE ACCENT}'), 1)
self.assertEqual(wlen('a\N{ZERO WIDTH JOINER}b'), 2)

def test_wlen_with_osc_sequence(self):
prompt = ">>> "
self.assertEqual(wlen("\x1b]633;A\x07" + prompt), len(prompt))

def test_wlen_with_unterminated_osc_sequence_as_individual_characters(self):
# \x1b is treated as a single character (ESC)
self.assertEqual(wlen("\x1b]633;A"), 7)
Comment thread
edvilme marked this conversation as resolved.

def test_prev_next_window(self):
def gen_normal():
yield 1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Correctly detect and handle OSC sequences in PyREPL.
Loading