https://github.com/python/cpython/commit/5f6fc462db73ed18eaa9520cbc275a3c5bbad746 commit: 5f6fc462db73ed18eaa9520cbc275a3c5bbad746 branch: 3.15 author: Miss Islington (bot) <[email protected]> committer: hugovk <[email protected]> date: 2026-09-15T09:45:59+03:00 summary:
[3.15] gh-154470: Fix spurious ^J in pdb's colorized list command (GH-154473) (#157536) gh-154470: Fix spurious ^J in pdb's colorized list command (GH-154473) (cherry picked from commit c68ce16cee99df38405da127eefe1eeb40d37e99) Co-authored-by: Ćukasz Langa <[email protected]> files: A Misc/NEWS.d/next/Library/2026-07-22-13-39-58.gh-issue-154470.zGcz6K.rst M Lib/pdb.py M Lib/test/test_pdb.py M Lib/test/test_remote_pdb.py diff --git a/Lib/pdb.py b/Lib/pdb.py index 458eb8352652366..e0c9b4d2c241a74 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_pdb.py b/Lib/test/test_pdb.py index 771abb8f871bab9..cfddd96a24fd7d9 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -4996,6 +4996,16 @@ def test_code_display(self): p.set_trace(commands=['ll', 'c']) self.assertNotIn("\x1b", output.getvalue()) + def test_list_does_not_colorize_trailing_newlines(self): + # Keep the marker split so it is not present in the listed source. + caret_newline = "^" + "J" + output = io.StringIO() + p = pdb.Pdb(stdout=output, colorize=True) + p.set_trace(commands=['list', 'continue']) + result = output.getvalue() + self.assertIn("\x1b", result) + self.assertNotIn(caret_newline, result) + def test_stack_entry(self): output = io.StringIO() p = pdb.Pdb(stdout=output, colorize=True) diff --git a/Lib/test/test_remote_pdb.py b/Lib/test/test_remote_pdb.py index 5b23a194098b01c..e5387526090536d 100644 --- a/Lib/test/test_remote_pdb.py +++ b/Lib/test/test_remote_pdb.py @@ -1337,6 +1337,55 @@ 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) + @unittest.skipUnless(pty, "requires pty") def test_prompt_with_interactive_terminal(self): """The server must send "(Pdb) " even when the target owns a terminal. 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. _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
