From 3607ea33aa34059818a4106e4e015e8fd106a397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Wed, 22 Jul 2026 13:40:15 +0200 Subject: [PATCH] gh-154470: Fix spurious ^J in pdb's colorized list command pdb's `list` colorizes each source line with `_colorize_code`, which feeds it to `_pyrepl.utils.disp_str`. `disp_str` renders control characters in caret notation, so the trailing newline of each source line became a literal "^J". `_print_lines` only stripped the line *after* colorizing, and rstrip() cannot remove the embedded caret sequence. Strip the line before colorizing, matching what `where` already does via `format_stack_entry`. Add a test that attaches with colorize enabled, runs `list`, and asserts no "^J" leaks into the source lines. Co-Authored-By: Claude Opus 4.8 (1M context) --- Lib/pdb.py | 6 ++- Lib/test/test_remote_pdb.py | 50 +++++++++++++++++++ ...-07-22-13-39-58.gh-issue-154470.zGcz6K.rst | 4 ++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-22-13-39-58.gh-issue-154470.zGcz6K.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index 01451f0229cacb2..a41a86704857cb4 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -2435,9 +2435,13 @@ def _print_lines(self, lines, start, breaks=(), frame=None): s += '->' elif lineno == exc_lineno: s += '>>' + # Strip the trailing newline before colorizing: the colorizer + # renders control characters (like '\n') in caret notation, so a + # later rstrip() could not remove the resulting '^J'. + line = line.rstrip() if self.colorize: line = self._colorize_code(line) - self.message(s + '\t' + line.rstrip()) + self.message(s + '\t' + line) def do_whatis(self, arg): """whatis expression diff --git a/Lib/test/test_remote_pdb.py b/Lib/test/test_remote_pdb.py index d26d63faa61ddb6..3f39f67b3fd3ce3 100644 --- a/Lib/test/test_remote_pdb.py +++ b/Lib/test/test_remote_pdb.py @@ -1292,6 +1292,56 @@ def test_handle_eof(self): self.assertEqual(process.returncode, 0) self.assertEqual(stderr, "") + def test_colorized_list_has_no_caret_encoded_newlines(self): + """A colorized ``list`` must not append "^J" to each source line. + + The remote server colorizes the source it sends to the client. The + colorizer renders control characters in caret notation, so a source + line's trailing newline has to be stripped *before* it is colorized; + otherwise every listed line ends with a spurious "^J". ``where`` was + unaffected because it strips the line before colorizing. See + gh-154470. + """ + # colorize=True is what attaching from a color-capable terminal passes + # to the server, and it is what makes the server colorize ``list``. + script = textwrap.dedent(f""" + import pdb, sys + def helper(): + x = 42 + return x + def connect(): + frame = sys._getframe() + pdb._connect( + host='127.0.0.1', + port={self.port}, + frame=frame, + commands="", + version=pdb._PdbServer.protocol_version(), + signal_raising_thread=False, + colorize=True, + ) + return helper() + connect() + """) + self._create_script(script=script) + process, client_file = self._connect_and_get_client_file() + + with kill_on_error(process): + self._read_until_prompt(client_file) + self._send_command(client_file, "l 1, 15") + messages = self._read_until_prompt(client_file) + source = "".join(m["message"] for m in messages if "message" in m) + + # Sanity: we really did receive colorized source ... + self.assertIn("helper", source) + self.assertIn("\x1b[", source) # ANSI color escapes are present + # ... and no trailing newline leaked through as caret notation. + self.assertNotIn("^J", source) + + self._send_command(client_file, "c") + process.wait(timeout=SHORT_TIMEOUT) + self.assertEqual(process.returncode, 0) + def test_protocol_version(self): """Test that incompatible protocol versions are properly detected.""" # Create a script using an incompatible protocol version diff --git a/Misc/NEWS.d/next/Library/2026-07-22-13-39-58.gh-issue-154470.zGcz6K.rst b/Misc/NEWS.d/next/Library/2026-07-22-13-39-58.gh-issue-154470.zGcz6K.rst new file mode 100644 index 000000000000000..e3d8e3c27cf6389 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-22-13-39-58.gh-issue-154470.zGcz6K.rst @@ -0,0 +1,4 @@ +Fixed a spurious ``^J`` printed at the end of every source line by the +``list`` command of :mod:`pdb` when the output is colorized (for example +when attaching to a running process). The source line is now stripped before +it is colorized, like the ``where`` command already did.