diff --git a/Lib/_pyrepl/utils.py b/Lib/_pyrepl/utils.py index 64708e843b685b..75d8cc05e4441d 100644 --- a/Lib/_pyrepl/utils.py +++ b/Lib/_pyrepl/utils.py @@ -329,8 +329,12 @@ def disp_str( for i, c in enumerate(buffer, start_index): if colors and colors[0].span.start == i: # new color starts now pre_color = theme[colors[0].tag] - - if c == "\x1a": # CTRL-Z on Windows + # gh-140502: properly handle tabs when pasting multiline text + if c == "\t": + width = 8 - (sum(char_widths) % 8) + chars.append(" " * width) + char_widths.append(width) + elif c == "\x1a": # CTRL-Z on Windows chars.append(c) char_widths.append(2) elif ord(c) < 128: diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index e298b2add52c3e..ddc039f5205982 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -37,6 +37,7 @@ ReadlineConfig, _ReadlineWrapper, ) +from _pyrepl.utils import disp_str from _pyrepl.readline import multiline_input as readline_multiline_input try: @@ -1911,3 +1912,19 @@ def test_ctrl_d_single_line_end_no_newline(self): ) reader, _ = handle_all_events(events) self.assertEqual("hello", "".join(reader.buffer)) + + +class TestDispStr(TestCase): + def test_disp_str_width_calculation(self): + test_cases = [ + ("\tX", [8, 1]), # column 0 -> 8 spaces + ("X\t", [1, 7]), # column 1 -> 7 spaces + ("ABC\tX", [1, 1, 1, 5, 1]), # column 3 -> 5 spaces + ("XXXXXXX\t", [1, 1, 1, 1, 1, 1, 1, 1]), # column 7 -> 1 space + ("XXXXXXXX\t", [1, 1, 1, 1, 1, 1, 1, 1, 8]), # column 8 -> 8 spaces + ("δΈ­\tX", [2, 6, 1]), # wide char + tab + ] + for buffer, expected_widths in test_cases: + with self.subTest(buffer=repr(buffer)): + _, widths = disp_str(buffer, 0) + self.assertEqual(widths, expected_widths) diff --git a/Misc/NEWS.d/next/Library/2025-10-28-17-15-30.gh-issue-140502.ilUv1f.rst b/Misc/NEWS.d/next/Library/2025-10-28-17-15-30.gh-issue-140502.ilUv1f.rst new file mode 100644 index 00000000000000..7f3186e450ec7f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-28-17-15-30.gh-issue-140502.ilUv1f.rst @@ -0,0 +1 @@ +Fix: default REPL ``disp_str`` does not handle tab right.