Skip to content

gh-118835: pyrepl: Fix prompt length computation for custom prompts containing ANSI escape codes #119942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 3, 2024
Merged
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
10 changes: 8 additions & 2 deletions Lib/_pyrepl/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@


from . import commands, console, input
from .utils import ANSI_ESCAPE_SEQUENCE, wlen
from .utils import ANSI_ESCAPE_SEQUENCE, wlen, str_width
from .trace import trace


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

def process_prompt(self, prompt: str) -> tuple[str, int]:
@staticmethod
def process_prompt(prompt: str) -> tuple[str, int]:
"""Process the prompt.

This means calculate the length of the prompt. The character \x01
Expand All @@ -351,6 +352,11 @@ def process_prompt(self, prompt: str) -> tuple[str, int]:
# sequences if they were not explicitly within \x01...\x02.
# They are CSI (or ANSI) sequences ( ESC [ ... LETTER )

# wlen from utils already excludes ANSI_ESCAPE_SEQUENCE chars,
# which breaks the logic below so we redefine it here.
def wlen(s: str) -> int:
return sum(str_width(i) for i in s)

out_prompt = ""
l = wlen(prompt)
pos = 0
Expand Down
32 changes: 32 additions & 0 deletions Lib/test/test_pyrepl/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader
from _pyrepl.console import Event
from _pyrepl.reader import Reader


class TestReader(TestCase):
Expand Down Expand Up @@ -176,3 +177,34 @@ def test_newline_within_block_trailing_whitespace(self):
)
self.assert_screen_equals(reader, expected)
self.assertTrue(reader.finished)

def test_prompt_length(self):
# Handles simple ASCII prompt
ps1 = ">>> "
prompt, l = Reader.process_prompt(ps1)
self.assertEqual(prompt, ps1)
self.assertEqual(l, 4)

# Handles ANSI escape sequences
ps1 = "\033[0;32m>>> \033[0m"
prompt, l = Reader.process_prompt(ps1)
self.assertEqual(prompt, "\033[0;32m>>> \033[0m")
self.assertEqual(l, 4)

# Handles ANSI escape sequences bracketed in \001 .. \002
ps1 = "\001\033[0;32m\002>>> \001\033[0m\002"
prompt, l = Reader.process_prompt(ps1)
self.assertEqual(prompt, "\033[0;32m>>> \033[0m")
self.assertEqual(l, 4)

# Handles wide characters in prompt
ps1 = "樂>> "
prompt, l = Reader.process_prompt(ps1)
self.assertEqual(prompt, ps1)
self.assertEqual(l, 5)

# Handles wide characters AND ANSI sequences together
ps1 = "\001\033[0;32m\002樂>\001\033[0m\002> "
prompt, l = Reader.process_prompt(ps1)
self.assertEqual(prompt, "\033[0;32m樂>\033[0m> ")
self.assertEqual(l, 5)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix _pyrepl crash when using custom prompt with ANSI escape codes.
Loading