Skip to content

Commit ff4959b

Browse files
pythongh-113081: Highlight source code in pdb (python#133355)
1 parent 08d7687 commit ff4959b

File tree

5 files changed

+74
-8
lines changed

5 files changed

+74
-8
lines changed

Doc/library/pdb.rst

+7-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ The ``run*`` functions and :func:`set_trace` are aliases for instantiating the
243243
access further features, you have to do this yourself:
244244

245245
.. class:: Pdb(completekey='tab', stdin=None, stdout=None, skip=None, \
246-
nosigint=False, readrc=True, mode=None, backend=None)
246+
nosigint=False, readrc=True, mode=None, backend=None, colorize=False)
247247

248248
:class:`Pdb` is the debugger class.
249249

@@ -273,6 +273,9 @@ access further features, you have to do this yourself:
273273
is passed, the default backend will be used. See :func:`set_default_backend`.
274274
Otherwise the supported backends are ``'settrace'`` and ``'monitoring'``.
275275

276+
The *colorize* argument, if set to ``True``, will enable colorized output in the
277+
debugger, if color is supported. This will highlight source code displayed in pdb.
278+
276279
Example call to enable tracing with *skip*::
277280

278281
import pdb; pdb.Pdb(skip=['django.*']).set_trace()
@@ -295,6 +298,9 @@ access further features, you have to do this yourself:
295298
.. versionadded:: 3.14
296299
Added the *backend* argument.
297300

301+
.. versionadded:: 3.14
302+
Added the *colorize* argument.
303+
298304
.. versionchanged:: 3.14
299305
Inline breakpoints like :func:`breakpoint` or :func:`pdb.set_trace` will
300306
always stop the program at calling frame, ignoring the *skip* pattern (if any).

Doc/whatsnew/3.14.rst

+5
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,11 @@ pdb
14361436
function.
14371437
(Contributed by Tian Gao in :gh:`132576`.)
14381438

1439+
* Source code displayed in :mod:`pdb` will be syntax-highlighted. This feature
1440+
can be controlled using the same methods as PyREPL, in addition to the newly
1441+
added ``colorize`` argument of :class:`pdb.Pdb`.
1442+
(Contributed by Tian Gao in :gh:`133355`.)
1443+
14391444

14401445
pickle
14411446
------

Lib/pdb.py

+24-7
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import traceback
9494
import linecache
9595
import _colorize
96+
import _pyrepl.utils
9697

9798
from contextlib import closing
9899
from contextlib import contextmanager
@@ -339,7 +340,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
339340
_last_pdb_instance = None
340341

341342
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
342-
nosigint=False, readrc=True, mode=None, backend=None):
343+
nosigint=False, readrc=True, mode=None, backend=None, colorize=False):
343344
bdb.Bdb.__init__(self, skip=skip, backend=backend if backend else get_default_backend())
344345
cmd.Cmd.__init__(self, completekey, stdin, stdout)
345346
sys.audit("pdb.Pdb")
@@ -352,6 +353,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
352353
self._wait_for_mainpyfile = False
353354
self.tb_lineno = {}
354355
self.mode = mode
356+
self.colorize = _colorize.can_colorize(file=stdout or sys.stdout) and colorize
355357
# Try to load readline if it exists
356358
try:
357359
import readline
@@ -1036,6 +1038,13 @@ def handle_command_def(self, line):
10361038
return True
10371039
return False
10381040

1041+
def _colorize_code(self, code):
1042+
if self.colorize:
1043+
colors = list(_pyrepl.utils.gen_colors(code))
1044+
chars, _ = _pyrepl.utils.disp_str(code, colors=colors)
1045+
code = "".join(chars)
1046+
return code
1047+
10391048
# interface abstraction functions
10401049

10411050
def message(self, msg, end='\n'):
@@ -2166,6 +2175,8 @@ def _print_lines(self, lines, start, breaks=(), frame=None):
21662175
s += '->'
21672176
elif lineno == exc_lineno:
21682177
s += '>>'
2178+
if self.colorize:
2179+
line = self._colorize_code(line)
21692180
self.message(s + '\t' + line.rstrip())
21702181

21712182
def do_whatis(self, arg):
@@ -2365,8 +2376,14 @@ def print_stack_entry(self, frame_lineno, prompt_prefix=line_prefix):
23652376
prefix = '> '
23662377
else:
23672378
prefix = ' '
2368-
self.message(prefix +
2369-
self.format_stack_entry(frame_lineno, prompt_prefix))
2379+
stack_entry = self.format_stack_entry(frame_lineno, prompt_prefix)
2380+
if self.colorize:
2381+
lines = stack_entry.split(prompt_prefix, 1)
2382+
if len(lines) > 1:
2383+
# We have some code to display
2384+
lines[1] = self._colorize_code(lines[1])
2385+
stack_entry = prompt_prefix.join(lines)
2386+
self.message(prefix + stack_entry)
23702387

23712388
# Provide help
23722389

@@ -2604,7 +2621,7 @@ def set_trace(*, header=None, commands=None):
26042621
if Pdb._last_pdb_instance is not None:
26052622
pdb = Pdb._last_pdb_instance
26062623
else:
2607-
pdb = Pdb(mode='inline', backend='monitoring')
2624+
pdb = Pdb(mode='inline', backend='monitoring', colorize=True)
26082625
if header is not None:
26092626
pdb.message(header)
26102627
pdb.set_trace(sys._getframe().f_back, commands=commands)
@@ -2619,7 +2636,7 @@ async def set_trace_async(*, header=None, commands=None):
26192636
if Pdb._last_pdb_instance is not None:
26202637
pdb = Pdb._last_pdb_instance
26212638
else:
2622-
pdb = Pdb(mode='inline', backend='monitoring')
2639+
pdb = Pdb(mode='inline', backend='monitoring', colorize=True)
26232640
if header is not None:
26242641
pdb.message(header)
26252642
await pdb.set_trace_async(sys._getframe().f_back, commands=commands)
@@ -2633,7 +2650,7 @@ def __init__(self, sockfile, owns_sockfile=True, **kwargs):
26332650
self._sockfile = sockfile
26342651
self._command_name_cache = []
26352652
self._write_failed = False
2636-
super().__init__(**kwargs)
2653+
super().__init__(colorize=False, **kwargs)
26372654

26382655
@staticmethod
26392656
def protocol_version():
@@ -3345,7 +3362,7 @@ def main():
33453362
# modified by the script being debugged. It's a bad idea when it was
33463363
# changed by the user from the command line. There is a "restart" command
33473364
# which allows explicit specification of command line arguments.
3348-
pdb = Pdb(mode='cli', backend='monitoring')
3365+
pdb = Pdb(mode='cli', backend='monitoring', colorize=True)
33493366
pdb.rcLines.extend(opts.commands)
33503367
while True:
33513368
try:

Lib/test/test_pdb.py

+37
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# A test suite for pdb; not very comprehensive at the moment.
22

3+
import _colorize
34
import doctest
45
import gc
6+
import io
57
import os
68
import pdb
79
import sys
@@ -3446,6 +3448,7 @@ def test_pdb_issue_gh_65052():
34463448
"""
34473449

34483450

3451+
@support.force_not_colorized_test_class
34493452
@support.requires_subprocess()
34503453
class PdbTestCase(unittest.TestCase):
34513454
def tearDown(self):
@@ -4688,6 +4691,40 @@ def foo():
46884691
self.assertIn("42", stdout)
46894692

46904693

4694+
@unittest.skipUnless(_colorize.can_colorize(), "Test requires colorize")
4695+
class PdbTestColorize(unittest.TestCase):
4696+
def setUp(self):
4697+
self._original_can_colorize = _colorize.can_colorize
4698+
# Force colorize to be enabled because we are sending data
4699+
# to a StringIO
4700+
_colorize.can_colorize = lambda *args, **kwargs: True
4701+
4702+
def tearDown(self):
4703+
_colorize.can_colorize = self._original_can_colorize
4704+
4705+
def test_code_display(self):
4706+
output = io.StringIO()
4707+
p = pdb.Pdb(stdout=output, colorize=True)
4708+
p.set_trace(commands=['ll', 'c'])
4709+
self.assertIn("\x1b", output.getvalue())
4710+
4711+
output = io.StringIO()
4712+
p = pdb.Pdb(stdout=output, colorize=False)
4713+
p.set_trace(commands=['ll', 'c'])
4714+
self.assertNotIn("\x1b", output.getvalue())
4715+
4716+
output = io.StringIO()
4717+
p = pdb.Pdb(stdout=output)
4718+
p.set_trace(commands=['ll', 'c'])
4719+
self.assertNotIn("\x1b", output.getvalue())
4720+
4721+
def test_stack_entry(self):
4722+
output = io.StringIO()
4723+
p = pdb.Pdb(stdout=output, colorize=True)
4724+
p.set_trace(commands=['w', 'c'])
4725+
self.assertIn("\x1b", output.getvalue())
4726+
4727+
46914728
@support.force_not_colorized_test_class
46924729
@support.requires_subprocess()
46934730
class TestREPLSession(unittest.TestCase):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Highlight syntax on source code in :mod:`pdb`.

0 commit comments

Comments
 (0)