1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """Customized version of pdb's default debugger.
19
20 - sets up a history file
21 - uses ipython if available to colorize lines of code
22 - overrides list command to search for current block instead
23 of using 5 lines of context
24
25
26
27
28 """
29
30 from __future__ import print_function
31
32 __docformat__ = "restructuredtext en"
33
34 try:
35 import readline
36 except ImportError:
37 readline = None
38 import os
39 import os.path as osp
40 import sys
41 from pdb import Pdb
42 import inspect
43
44 from logilab.common.compat import StringIO
45
46 try:
47 from IPython import PyColorize
48 except ImportError:
50 """fallback colorize function"""
51 return source
54 else:
55 - def colorize(source, start_lineno, curlineno):
56 """colorize and annotate source with linenos
57 (as in pdb's list command)
58 """
59 parser = PyColorize.Parser()
60 output = StringIO()
61 parser.format(source, output)
62 annotated = []
63 for index, line in enumerate(output.getvalue().splitlines()):
64 lineno = index + start_lineno
65 if lineno == curlineno:
66 annotated.append('%4s\t->\t%s' % (lineno, line))
67 else:
68 annotated.append('%4s\t\t%s' % (lineno, line))
69 return '\n'.join(annotated)
70
72 """colorize given source"""
73 parser = PyColorize.Parser()
74 output = StringIO()
75 parser.format(source, output)
76 return output.getvalue()
77
78
80 """Return the text of the source code for an object.
81
82 The argument may be a module, class, method, function, traceback, frame,
83 or code object. The source code is returned as a single string. An
84 IOError is raised if the source code cannot be retrieved."""
85 lines, lnum = inspect.getsourcelines(obj)
86 return ''.join(lines), lnum
87
88
89
91 """custom debugger
92
93 - sets up a history file
94 - uses ipython if available to colorize lines of code
95 - overrides list command to search for current block instead
96 of using 5 lines of context
97 """
99 Pdb.__init__(self)
100 self.reset()
101 if tcbk:
102 while tcbk.tb_next is not None:
103 tcbk = tcbk.tb_next
104 self._tcbk = tcbk
105 self._histfile = os.path.expanduser("~/.pdbhist")
106
108 """if readline is available, read pdb history file
109 """
110 if readline is not None:
111 try:
112
113
114 readline.read_history_file(self._histfile)
115 except IOError:
116 pass
117
119 """starts the interactive mode"""
120 self.interaction(self._tcbk.tb_frame, self._tcbk)
121
122 - def setup(self, frame, tcbk):
126
128 """quit hook: save commands in the history file"""
129 if readline is not None:
130 readline.write_history_file(self._histfile)
131 Pdb.set_quit(self)
132
133 - def complete_p(self, text, line, begin_idx, end_idx):
134 """provide variable names completion for the ``p`` command"""
135 namespace = dict(self.curframe.f_globals)
136 namespace.update(self.curframe.f_locals)
137 if '.' in text:
138 return self.attr_matches(text, namespace)
139 return [varname for varname in namespace if varname.startswith(text)]
140
141
143 """implementation coming from rlcompleter.Completer.attr_matches
144 Compute matches when text contains a dot.
145
146 Assuming the text is of the form NAME.NAME....[NAME], and is
147 evaluatable in self.namespace, it will be evaluated and its attributes
148 (as revealed by dir()) are used as possible completions. (For class
149 instances, class members are also considered.)
150
151 WARNING: this can still invoke arbitrary C code, if an object
152 with a __getattr__ hook is evaluated.
153
154 """
155 import re
156 m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
157 if not m:
158 return
159 expr, attr = m.group(1, 3)
160 object = eval(expr, namespace)
161 words = dir(object)
162 if hasattr(object, '__class__'):
163 words.append('__class__')
164 words = words + self.get_class_members(object.__class__)
165 matches = []
166 n = len(attr)
167 for word in words:
168 if word[:n] == attr and word != "__builtins__":
169 matches.append("%s.%s" % (expr, word))
170 return matches
171
173 """implementation coming from rlcompleter.get_class_members"""
174 ret = dir(klass)
175 if hasattr(klass, '__bases__'):
176 for base in klass.__bases__:
177 ret = ret + self.get_class_members(base)
178 return ret
179
180
182 """overrides default list command to display the surrounding block
183 instead of 5 lines of context
184 """
185 self.lastcmd = 'list'
186 if not arg:
187 try:
188 source, start_lineno = getsource(self.curframe)
189 print(colorize(''.join(source), start_lineno,
190 self.curframe.f_lineno))
191 except KeyboardInterrupt:
192 pass
193 except IOError:
194 Pdb.do_list(self, arg)
195 else:
196 Pdb.do_list(self, arg)
197 do_l = do_list
198
200 """opens source file corresponding to the current stack level"""
201 filename = self.curframe.f_code.co_filename
202 lineno = self.curframe.f_lineno
203 cmd = 'emacsclient --no-wait +%s %s' % (lineno, filename)
204 os.system(cmd)
205
206 do_o = do_open
207
209 """use our custom debugger"""
210 dbg = Debugger(sys.last_traceback)
211 dbg.start()
212
215