Skip to content

Commit 2e0aa73

Browse files
authored
gh-118835: pyrepl: Fix prompt length computation for custom prompts containing ANSI escape codes (#119942)
1 parent 4e8aa32 commit 2e0aa73

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

Lib/_pyrepl/reader.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929

3030
from . import commands, console, input
31-
from .utils import ANSI_ESCAPE_SEQUENCE, wlen
31+
from .utils import ANSI_ESCAPE_SEQUENCE, wlen, str_width
3232
from .trace import trace
3333

3434

@@ -339,7 +339,8 @@ def calc_complete_screen(self) -> list[str]:
339339
screeninfo.append((0, []))
340340
return screen
341341

342-
def process_prompt(self, prompt: str) -> tuple[str, int]:
342+
@staticmethod
343+
def process_prompt(prompt: str) -> tuple[str, int]:
343344
"""Process the prompt.
344345
345346
This means calculate the length of the prompt. The character \x01
@@ -351,6 +352,11 @@ def process_prompt(self, prompt: str) -> tuple[str, int]:
351352
# sequences if they were not explicitly within \x01...\x02.
352353
# They are CSI (or ANSI) sequences ( ESC [ ... LETTER )
353354

355+
# wlen from utils already excludes ANSI_ESCAPE_SEQUENCE chars,
356+
# which breaks the logic below so we redefine it here.
357+
def wlen(s: str) -> int:
358+
return sum(str_width(i) for i in s)
359+
354360
out_prompt = ""
355361
l = wlen(prompt)
356362
pos = 0

Lib/test/test_pyrepl/test_reader.py

+32
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader
66
from _pyrepl.console import Event
7+
from _pyrepl.reader import Reader
78

89

910
class TestReader(TestCase):
@@ -176,3 +177,34 @@ def test_newline_within_block_trailing_whitespace(self):
176177
)
177178
self.assert_screen_equals(reader, expected)
178179
self.assertTrue(reader.finished)
180+
181+
def test_prompt_length(self):
182+
# Handles simple ASCII prompt
183+
ps1 = ">>> "
184+
prompt, l = Reader.process_prompt(ps1)
185+
self.assertEqual(prompt, ps1)
186+
self.assertEqual(l, 4)
187+
188+
# Handles ANSI escape sequences
189+
ps1 = "\033[0;32m>>> \033[0m"
190+
prompt, l = Reader.process_prompt(ps1)
191+
self.assertEqual(prompt, "\033[0;32m>>> \033[0m")
192+
self.assertEqual(l, 4)
193+
194+
# Handles ANSI escape sequences bracketed in \001 .. \002
195+
ps1 = "\001\033[0;32m\002>>> \001\033[0m\002"
196+
prompt, l = Reader.process_prompt(ps1)
197+
self.assertEqual(prompt, "\033[0;32m>>> \033[0m")
198+
self.assertEqual(l, 4)
199+
200+
# Handles wide characters in prompt
201+
ps1 = "樂>> "
202+
prompt, l = Reader.process_prompt(ps1)
203+
self.assertEqual(prompt, ps1)
204+
self.assertEqual(l, 5)
205+
206+
# Handles wide characters AND ANSI sequences together
207+
ps1 = "\001\033[0;32m\002樂>\001\033[0m\002> "
208+
prompt, l = Reader.process_prompt(ps1)
209+
self.assertEqual(prompt, "\033[0;32m樂>\033[0m> ")
210+
self.assertEqual(l, 5)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix _pyrepl crash when using custom prompt with ANSI escape codes.

0 commit comments

Comments
 (0)