Skip to content

Commit 3607ea3

Browse files
ambvclaude
andcommitted
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) <noreply@anthropic.com>
1 parent d1174a4 commit 3607ea3

3 files changed

Lines changed: 59 additions & 1 deletion

File tree

Lib/pdb.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2435,9 +2435,13 @@ def _print_lines(self, lines, start, breaks=(), frame=None):
24352435
s += '->'
24362436
elif lineno == exc_lineno:
24372437
s += '>>'
2438+
# Strip the trailing newline before colorizing: the colorizer
2439+
# renders control characters (like '\n') in caret notation, so a
2440+
# later rstrip() could not remove the resulting '^J'.
2441+
line = line.rstrip()
24382442
if self.colorize:
24392443
line = self._colorize_code(line)
2440-
self.message(s + '\t' + line.rstrip())
2444+
self.message(s + '\t' + line)
24412445

24422446
def do_whatis(self, arg):
24432447
"""whatis expression

Lib/test/test_remote_pdb.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,56 @@ def test_handle_eof(self):
12921292
self.assertEqual(process.returncode, 0)
12931293
self.assertEqual(stderr, "")
12941294

1295+
def test_colorized_list_has_no_caret_encoded_newlines(self):
1296+
"""A colorized ``list`` must not append "^J" to each source line.
1297+
1298+
The remote server colorizes the source it sends to the client. The
1299+
colorizer renders control characters in caret notation, so a source
1300+
line's trailing newline has to be stripped *before* it is colorized;
1301+
otherwise every listed line ends with a spurious "^J". ``where`` was
1302+
unaffected because it strips the line before colorizing. See
1303+
gh-154470.
1304+
"""
1305+
# colorize=True is what attaching from a color-capable terminal passes
1306+
# to the server, and it is what makes the server colorize ``list``.
1307+
script = textwrap.dedent(f"""
1308+
import pdb, sys
1309+
def helper():
1310+
x = 42
1311+
return x
1312+
def connect():
1313+
frame = sys._getframe()
1314+
pdb._connect(
1315+
host='127.0.0.1',
1316+
port={self.port},
1317+
frame=frame,
1318+
commands="",
1319+
version=pdb._PdbServer.protocol_version(),
1320+
signal_raising_thread=False,
1321+
colorize=True,
1322+
)
1323+
return helper()
1324+
connect()
1325+
""")
1326+
self._create_script(script=script)
1327+
process, client_file = self._connect_and_get_client_file()
1328+
1329+
with kill_on_error(process):
1330+
self._read_until_prompt(client_file)
1331+
self._send_command(client_file, "l 1, 15")
1332+
messages = self._read_until_prompt(client_file)
1333+
source = "".join(m["message"] for m in messages if "message" in m)
1334+
1335+
# Sanity: we really did receive colorized source ...
1336+
self.assertIn("helper", source)
1337+
self.assertIn("\x1b[", source) # ANSI color escapes are present
1338+
# ... and no trailing newline leaked through as caret notation.
1339+
self.assertNotIn("^J", source)
1340+
1341+
self._send_command(client_file, "c")
1342+
process.wait(timeout=SHORT_TIMEOUT)
1343+
self.assertEqual(process.returncode, 0)
1344+
12951345
def test_protocol_version(self):
12961346
"""Test that incompatible protocol versions are properly detected."""
12971347
# Create a script using an incompatible protocol version
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixed a spurious ``^J`` printed at the end of every source line by the
2+
``list`` command of :mod:`pdb` when the output is colorized (for example
3+
when attaching to a running process). The source line is now stripped before
4+
it is colorized, like the ``where`` command already did.

0 commit comments

Comments
 (0)