gh-118835: pyrepl: Fix prompt length computation for custom prompts containing ANSI escape codes (#119942)
This commit is contained in:
parent
4e8aa32245
commit
2e0aa731ae
@ -28,7 +28,7 @@ from _colorize import can_colorize, ANSIColors # type: ignore[import-not-found]
|
|||||||
|
|
||||||
|
|
||||||
from . import commands, console, input
|
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
|
from .trace import trace
|
||||||
|
|
||||||
|
|
||||||
@ -339,7 +339,8 @@ class Reader:
|
|||||||
screeninfo.append((0, []))
|
screeninfo.append((0, []))
|
||||||
return screen
|
return screen
|
||||||
|
|
||||||
def process_prompt(self, prompt: str) -> tuple[str, int]:
|
@staticmethod
|
||||||
|
def process_prompt(prompt: str) -> tuple[str, int]:
|
||||||
"""Process the prompt.
|
"""Process the prompt.
|
||||||
|
|
||||||
This means calculate the length of the prompt. The character \x01
|
This means calculate the length of the prompt. The character \x01
|
||||||
@ -351,6 +352,11 @@ class Reader:
|
|||||||
# sequences if they were not explicitly within \x01...\x02.
|
# sequences if they were not explicitly within \x01...\x02.
|
||||||
# They are CSI (or ANSI) sequences ( ESC [ ... LETTER )
|
# 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 = ""
|
out_prompt = ""
|
||||||
l = wlen(prompt)
|
l = wlen(prompt)
|
||||||
pos = 0
|
pos = 0
|
||||||
|
@ -4,6 +4,7 @@ from unittest import TestCase
|
|||||||
|
|
||||||
from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader
|
from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader
|
||||||
from _pyrepl.console import Event
|
from _pyrepl.console import Event
|
||||||
|
from _pyrepl.reader import Reader
|
||||||
|
|
||||||
|
|
||||||
class TestReader(TestCase):
|
class TestReader(TestCase):
|
||||||
@ -176,3 +177,34 @@ class TestReader(TestCase):
|
|||||||
)
|
)
|
||||||
self.assert_screen_equals(reader, expected)
|
self.assert_screen_equals(reader, expected)
|
||||||
self.assertTrue(reader.finished)
|
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)
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
Fix _pyrepl crash when using custom prompt with ANSI escape codes.
|
Loading…
x
Reference in New Issue
Block a user