Skip to content
Closed
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: 14 additions & 2 deletions Lib/_pyrepl/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,14 @@ def iter_display_chars(
buffer: str,
colors: list[ColorSpan] | None = None,
start_index: int = 0,
*,
escape: bool = True,
) -> Iterator[StyledChar]:
"""Yield visible display characters with widths and semantic color tags.

With ``escape=True`` (default) ASCII control chars are rewritten to caret
notation (``\\n`` -> ``^J``); pass ``escape=False`` to keep them verbatim.

Note: ``colors`` is consumed in place as spans are processed -- callers
that split a buffer across multiple calls rely on this mutation to track
which spans have already been handled.
Expand All @@ -331,7 +336,7 @@ def iter_display_chars(
if colors and color_idx < len(colors) and colors[color_idx].span.start == i:
active_tag = colors[color_idx].tag

if control := _ascii_control_repr(c):
if escape and (control := _ascii_control_repr(c)):
text = control
width = len(control)
elif ord(c) < 128:
Expand Down Expand Up @@ -363,6 +368,8 @@ def disp_str(
colors: list[ColorSpan] | None = None,
start_index: int = 0,
force_color: bool = False,
*,
escape: bool = True,
) -> tuple[CharBuffer, CharWidths]:
r"""Decompose the input buffer into a printable variant with applied colors.

Expand All @@ -374,6 +381,9 @@ def disp_str(
- the second list is the visible width of each character in the input
buffer.

With ``escape=True`` (default) ASCII control chars are rewritten to caret
notation (``\\n`` -> ``^J``); pass ``escape=False`` to keep them verbatim.

Note on colors:
- The `colors` list, if provided, is partially consumed within. We're using
a list and not a generator since we need to hold onto the current
Expand All @@ -393,7 +403,9 @@ def disp_str(
(['\x1b[1;34mw', 'h', 'i', 'l', 'e\x1b[0m', ' ', '1', ':'], [1, 1, 1, 1, 1, 1, 1, 1])

"""
styled_chars = list(iter_display_chars(buffer, colors, start_index))
styled_chars = list(
iter_display_chars(buffer, colors, start_index, escape=escape)
)
chars: CharBuffer = []
char_widths: CharWidths = []
theme = THEME(force_color=force_color)
Expand Down
7 changes: 6 additions & 1 deletion Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1268,7 +1268,12 @@ def handle_command_def(self, line):
def _colorize_code(self, code):
if self.colorize and _pyrepl:
colors = list(_pyrepl.utils.gen_colors(code))
chars, _ = _pyrepl.utils.disp_str(code, colors=colors, force_color=True)
chars, _ = _pyrepl.utils.disp_str(
code,
colors=colors,
force_color=True,
escape=False
)
code = "".join(chars)
return code

Expand Down
17 changes: 16 additions & 1 deletion Lib/test/test_pyrepl/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from unittest import TestCase

from _pyrepl.utils import str_width, wlen, prev_next_window, gen_colors
from _pyrepl.utils import (
disp_str,
gen_colors,
prev_next_window,
str_width,
wlen,
)


class TestUtils(TestCase):
Expand Down Expand Up @@ -159,3 +165,12 @@ def test_gen_colors_keyword_highlighting(self):
span_text = code[color.span.start:color.span.end + 1]
actual_highlights.append((span_text, color.tag))
self.assertEqual(actual_highlights, expected_highlights)

def test_disp_str_escape(self):
# default: control chars become caret notation
chars, _ = disp_str("a\nb\tc\x1bd")
self.assertEqual("".join(chars), "a^Jb^Ic^[d")

# escape=False: control chars pass through verbatim
chars, _ = disp_str("a\nb\tc\x1bd", escape=False)
self.assertEqual("".join(chars), "a\nb\tc\x1bd")