Try this.
In your .pdbrc
file
import os
exec(open(os.path.expanduser("~/.pdbrc.py")).read())
alias n next ;; list_restore_lastcmd .
alias s step ;; list_restore_lastcmd .
alias u up ;; list_restore_lastcmd .
alias d down ;; list_restore_lastcmd .
alias r return ;; list_restore_lastcmd .
Then in your .pdbrc.py
file
import pdb
import inspect
actual_precmd_source = inspect.getsource(pdb.Pdb.precmd)
# Store this source code string in pdb.Pdb object, so that it can be used later
# on by the monkeypatch functions.
pdb.Pdb._reserverd_actual_precmd_source = actual_precmd_source
def new_precmd(self, line, *args, **kwargs):
# This function calls the original precmd function,
# but also stores the `self.lastcmd` at the time of the call in a new member variable.
# This new state end up being the last-last-command.
# This function is monkeypatched in the most bizarre way.
# Since this function ends up calling its own original implementation, we
# cannot simply override it.
# If we do, it becomes an infinite recursion.
# So instead, we read the code of the original function in a string with
# `inspect` package,
# do some string manipulation to build a trampoline function's source code
# and then execute that function.
# These re-imports are required because otherwise falling into pdb with a
# breakpoint() instruction does not see the top-of-file imports.
import pdb
original_source = pdb.Pdb._reserverd_actual_precmd_source
ss = f"""
def trampoline_precmd(self, line, *args, **kwargs):
{original_source}
# Store the last-last-command
self._reserved_last_last_cmd = self.lastcmd
return precmd(self, line, *args, **kwargs)
"""
d = {}
exec(ss, d)
return d["trampoline_precmd"](self, line, *args, **kwargs)
def do_list_restore_lastcmd(self, arg):
# A simple monkeypatch function to call the actual list function,
# but without recording the execution in the self.lastcmd state.
# At the end, it restores the lastcmd state to the actual previously
# executed command that was specified by the user.
# To do that, it uses the newly added _reserved_last_last_cmd state, which
# is now maintained in the monkeypatched precmd function.
self.do_list(arg)
self.lastcmd = self._reserved_last_last_cmd
def new_emptyline(self):
# This monkeypatch function handles the use case where the user merely
# presses RET on an empty line to execute the previous command as is.
# That is a pretty common use case in command line debuggers.
# We only support the `next` command and whenever we encounter the next
# command that is indirectly executed via an empty line and RET, we enqueue
# a `list` command right after.
# The `list` command we enqueue does not record itself in the self.lastcmd history,
# so this becomes hidden from the user.
# Hence the user can press RET again to execute the original `next` command again.
# These re-imports are required because otherwise falling into pdb with a
# breakpoint() instruction does not see the top-of-file imports.
import pdb
rv = super(pdb.Pdb, self).emptyline()
# If the repeated command is next, then also print the context
if self.lastcmd in ["next"]:
self.cmdqueue.append("list_restore_lastcmd .")
return rv
# Monkeypatch the functions, using the approach here https://stackoverflow.com/a/19546169/5771861
pdb.Pdb.precmd = new_precmd
pdb.Pdb.do_list_restore_lastcmd = do_list_restore_lastcmd
pdb.Pdb.emptyline = new_emptyline
This ends up being quite a nuanced monkeypatch that becomes somewhat implementation dependent of the pdb.Pdb class. So this may not fit everyone's expectations. Definitely not a zero-maintenance approach.
But this leaves a reasonable experience with command repeats and doing the context printing in an invisible way.
For example debugging the following script
def complex_calculation(a, b, c, d, e):
f = a + b
g = c * d
h = e - f
i = g / h
j = i ** 2
k = abs(j - g)
return k
def bubble_sort(array):
n = len(array)
for i in range(n):
already_sorted = True
for j in range(n - i - 1):
if array[j] > array[j + 1]:
array[j], array[j + 1] = array[j + 1], array[j]
already_sorted = False
if already_sorted:
break
return array
out = complex_calculation(1, 2, 3, 4, 5)
out = bubble_sort([1, 5, 2, 7, 3, 9, 4, 6, 8])
Looks like the following in the pdb command line
hakan@hakan:~/misc$ python3 -m pdb dummy.py
> /Users/hakan/misc/dummy.py(2)<module>()
-> def complex_calculation(a, b, c, d, e):
(Pdb) n
> /Users/hakan/misc/dummy.py(13)<module>()
-> def bubble_sort(array):
8 k = abs(j - g)
9 return k
10
11
12
13 -> def bubble_sort(array):
14 n = len(array)
15
16 for i in range(n):
17 already_sorted = True
18
(Pdb) n
> /Users/hakan/misc/dummy.py(30)<module>()
-> out = complex_calculation(1, 2, 3, 4, 5)
25 break
26
27 return array
28
29
30 -> out = complex_calculation(1, 2, 3, 4, 5)
31 out = bubble_sort([1, 5, 2, 7, 3, 9, 4, 6, 8])
32
33
34
35
(Pdb) s
--Call--
> /Users/hakan/misc/dummy.py(2)complex_calculation()
-> def complex_calculation(a, b, c, d, e):
1
2 -> def complex_calculation(a, b, c, d, e):
3 f = a + b
4 g = c * d
5 h = e - f
6 i = g / h
7 j = i ** 2
8 k = abs(j - g)
9 return k
10
11
(Pdb) n
> /Users/hakan/misc/dummy.py(3)complex_calculation()
-> f = a + b
1
2 def complex_calculation(a, b, c, d, e):
3 -> f = a + b
4 g = c * d
5 h = e - f
6 i = g / h
7 j = i ** 2
8 k = abs(j - g)
9 return k
10
11
(Pdb)
> /Users/hakan/misc/dummy.py(4)complex_calculation()
-> g = c * d
1
2 def complex_calculation(a, b, c, d, e):
3 f = a + b
4 -> g = c * d
5 h = e - f
6 i = g / h
7 j = i ** 2
8 k = abs(j - g)
9 return k
10
11
(Pdb)
> /Users/hakan/misc/dummy.py(5)complex_calculation()
-> h = e - f
1
2 def complex_calculation(a, b, c, d, e):
3 f = a + b
4 g = c * d
5 -> h = e - f
6 i = g / h
7 j = i ** 2
8 k = abs(j - g)
9 return k
10
11
(Pdb)
> /Users/hakan/misc/dummy.py(6)complex_calculation()
-> i = g / h
1
2 def complex_calculation(a, b, c, d, e):
3 f = a + b
4 g = c * d
5 h = e - f
6 -> i = g / h
7 j = i ** 2
8 k = abs(j - g)
9 return k
10
11
(Pdb) r
--Return--
> /Users/hakan/misc/dummy.py(9)complex_calculation()->24.0
-> return k
4 g = c * d
5 h = e - f
6 i = g / h
7 j = i ** 2
8 k = abs(j - g)
9 -> return k
10
11
12
13 def bubble_sort(array):
14 n = len(array)
(Pdb) n
> /Users/hakan/misc/dummy.py(31)<module>()
-> out = bubble_sort([1, 5, 2, 7, 3, 9, 4, 6, 8])
26
27 return array
28
29
30 out = complex_calculation(1, 2, 3, 4, 5)
31 -> out = bubble_sort([1, 5, 2, 7, 3, 9, 4, 6, 8])
32
33
34
35
36 print(out)
(Pdb) s
--Call--
> /Users/hakan/misc/dummy.py(13)bubble_sort()
-> def bubble_sort(array):
8 k = abs(j - g)
9 return k
10
11
12
13 -> def bubble_sort(array):
14 n = len(array)
15
16 for i in range(n):
17 already_sorted = True
18
(Pdb) n
> /Users/hakan/misc/dummy.py(14)bubble_sort()
-> n = len(array)
9 return k
10
11
12
13 def bubble_sort(array):
14 -> n = len(array)
15
16 for i in range(n):
17 already_sorted = True
18
19 for j in range(n - i - 1):
(Pdb)
> /Users/hakan/misc/dummy.py(16)bubble_sort()
-> for i in range(n):
11
12
13 def bubble_sort(array):
14 n = len(array)
15
16 -> for i in range(n):
17 already_sorted = True
18
19 for j in range(n - i - 1):
20 if array[j] > array[j + 1]:
21 array[j], array[j + 1] = array[j + 1], array[j]
(Pdb)
> /Users/hakan/misc/dummy.py(17)bubble_sort()
-> already_sorted = True
12
13 def bubble_sort(array):
14 n = len(array)
15
16 for i in range(n):
17 -> already_sorted = True
18
19 for j in range(n - i - 1):
20 if array[j] > array[j + 1]:
21 array[j], array[j + 1] = array[j + 1], array[j]
22 already_sorted = False
(Pdb)
> /Users/hakan/misc/dummy.py(19)bubble_sort()
-> for j in range(n - i - 1):
14 n = len(array)
15
16 for i in range(n):
17 already_sorted = True
18
19 -> for j in range(n - i - 1):
20 if array[j] > array[j + 1]:
21 array[j], array[j + 1] = array[j + 1], array[j]
22 already_sorted = False
23
24 if already_sorted:
(Pdb)
> /Users/hakan/misc/dummy.py(20)bubble_sort()
-> if array[j] > array[j + 1]:
15
16 for i in range(n):
17 already_sorted = True
18
19 for j in range(n - i - 1):
20 -> if array[j] > array[j + 1]:
21 array[j], array[j + 1] = array[j + 1], array[j]
22 already_sorted = False
23
24 if already_sorted:
25 break
(Pdb)