##// END OF EJS Templates
Reify colors from debugger. (#14387)...
M Bussonnier -
r28718:38aa68c3 merge
parent child Browse files
Show More
@@ -1,1105 +1,1090 b''
1 """
1 """
2 Pdb debugger class.
2 Pdb debugger class.
3
3
4
4
5 This is an extension to PDB which adds a number of new features.
5 This is an extension to PDB which adds a number of new features.
6 Note that there is also the `IPython.terminal.debugger` class which provides UI
6 Note that there is also the `IPython.terminal.debugger` class which provides UI
7 improvements.
7 improvements.
8
8
9 We also strongly recommend to use this via the `ipdb` package, which provides
9 We also strongly recommend to use this via the `ipdb` package, which provides
10 extra configuration options.
10 extra configuration options.
11
11
12 Among other things, this subclass of PDB:
12 Among other things, this subclass of PDB:
13 - supports many IPython magics like pdef/psource
13 - supports many IPython magics like pdef/psource
14 - hide frames in tracebacks based on `__tracebackhide__`
14 - hide frames in tracebacks based on `__tracebackhide__`
15 - allows to skip frames based on `__debuggerskip__`
15 - allows to skip frames based on `__debuggerskip__`
16
16
17 The skipping and hiding frames are configurable via the `skip_predicates`
17 The skipping and hiding frames are configurable via the `skip_predicates`
18 command.
18 command.
19
19
20 By default, frames from readonly files will be hidden, frames containing
20 By default, frames from readonly files will be hidden, frames containing
21 ``__tracebackhide__=True`` will be hidden.
21 ``__tracebackhide__=True`` will be hidden.
22
22
23 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
23 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
24 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
24 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
25
25
26 >>> def helpers_helper():
26 >>> def helpers_helper():
27 ... pass
27 ... pass
28 ...
28 ...
29 ... def helper_1():
29 ... def helper_1():
30 ... print("don't step in me")
30 ... print("don't step in me")
31 ... helpers_helpers() # will be stepped over unless breakpoint set.
31 ... helpers_helpers() # will be stepped over unless breakpoint set.
32 ...
32 ...
33 ...
33 ...
34 ... def helper_2():
34 ... def helper_2():
35 ... print("in me neither")
35 ... print("in me neither")
36 ...
36 ...
37
37
38 One can define a decorator that wraps a function between the two helpers:
38 One can define a decorator that wraps a function between the two helpers:
39
39
40 >>> def pdb_skipped_decorator(function):
40 >>> def pdb_skipped_decorator(function):
41 ...
41 ...
42 ...
42 ...
43 ... def wrapped_fn(*args, **kwargs):
43 ... def wrapped_fn(*args, **kwargs):
44 ... __debuggerskip__ = True
44 ... __debuggerskip__ = True
45 ... helper_1()
45 ... helper_1()
46 ... __debuggerskip__ = False
46 ... __debuggerskip__ = False
47 ... result = function(*args, **kwargs)
47 ... result = function(*args, **kwargs)
48 ... __debuggerskip__ = True
48 ... __debuggerskip__ = True
49 ... helper_2()
49 ... helper_2()
50 ... # setting __debuggerskip__ to False again is not necessary
50 ... # setting __debuggerskip__ to False again is not necessary
51 ... return result
51 ... return result
52 ...
52 ...
53 ... return wrapped_fn
53 ... return wrapped_fn
54
54
55 When decorating a function, ipdb will directly step into ``bar()`` by
55 When decorating a function, ipdb will directly step into ``bar()`` by
56 default:
56 default:
57
57
58 >>> @foo_decorator
58 >>> @foo_decorator
59 ... def bar(x, y):
59 ... def bar(x, y):
60 ... return x * y
60 ... return x * y
61
61
62
62
63 You can toggle the behavior with
63 You can toggle the behavior with
64
64
65 ipdb> skip_predicates debuggerskip false
65 ipdb> skip_predicates debuggerskip false
66
66
67 or configure it in your ``.pdbrc``
67 or configure it in your ``.pdbrc``
68
68
69
69
70
70
71 License
71 License
72 -------
72 -------
73
73
74 Modified from the standard pdb.Pdb class to avoid including readline, so that
74 Modified from the standard pdb.Pdb class to avoid including readline, so that
75 the command line completion of other programs which include this isn't
75 the command line completion of other programs which include this isn't
76 damaged.
76 damaged.
77
77
78 In the future, this class will be expanded with improvements over the standard
78 In the future, this class will be expanded with improvements over the standard
79 pdb.
79 pdb.
80
80
81 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
81 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
82 with minor changes. Licensing should therefore be under the standard Python
82 with minor changes. Licensing should therefore be under the standard Python
83 terms. For details on the PSF (Python Software Foundation) standard license,
83 terms. For details on the PSF (Python Software Foundation) standard license,
84 see:
84 see:
85
85
86 https://docs.python.org/2/license.html
86 https://docs.python.org/2/license.html
87
87
88
88
89 All the changes since then are under the same license as IPython.
89 All the changes since then are under the same license as IPython.
90
90
91 """
91 """
92
92
93 #*****************************************************************************
93 #*****************************************************************************
94 #
94 #
95 # This file is licensed under the PSF license.
95 # This file is licensed under the PSF license.
96 #
96 #
97 # Copyright (C) 2001 Python Software Foundation, www.python.org
97 # Copyright (C) 2001 Python Software Foundation, www.python.org
98 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
98 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
99 #
99 #
100 #
100 #
101 #*****************************************************************************
101 #*****************************************************************************
102
102
103 from __future__ import annotations
103 from __future__ import annotations
104
104
105 import inspect
105 import inspect
106 import linecache
106 import linecache
107 import os
107 import os
108 import re
108 import re
109 import sys
109 import sys
110 from contextlib import contextmanager
110 from contextlib import contextmanager
111 from functools import lru_cache
111 from functools import lru_cache
112
112
113 from IPython import get_ipython
113 from IPython import get_ipython
114 from IPython.core.excolors import exception_colors
114 from IPython.core.excolors import exception_colors
115 from IPython.utils import PyColorize, coloransi, py3compat
115 from IPython.utils import PyColorize, coloransi, py3compat
116
116
117 from typing import TYPE_CHECKING
117 from typing import TYPE_CHECKING
118
118
119 if TYPE_CHECKING:
119 if TYPE_CHECKING:
120 # otherwise circular import
120 # otherwise circular import
121 from IPython.core.interactiveshell import InteractiveShell
121 from IPython.core.interactiveshell import InteractiveShell
122
122
123 # skip module docstests
123 # skip module docstests
124 __skip_doctest__ = True
124 __skip_doctest__ = True
125
125
126 prompt = 'ipdb> '
126 prompt = 'ipdb> '
127
127
128 # We have to check this directly from sys.argv, config struct not yet available
128 # We have to check this directly from sys.argv, config struct not yet available
129 from pdb import Pdb as OldPdb
129 from pdb import Pdb as OldPdb
130
130
131 # Allow the set_trace code to operate outside of an ipython instance, even if
131 # Allow the set_trace code to operate outside of an ipython instance, even if
132 # it does so with some limitations. The rest of this support is implemented in
132 # it does so with some limitations. The rest of this support is implemented in
133 # the Tracer constructor.
133 # the Tracer constructor.
134
134
135 DEBUGGERSKIP = "__debuggerskip__"
135 DEBUGGERSKIP = "__debuggerskip__"
136
136
137
137
138 # this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676
138 # this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676
139 # on lower python versions, we backported the feature.
139 # on lower python versions, we backported the feature.
140 CHAIN_EXCEPTIONS = sys.version_info < (3, 13)
140 CHAIN_EXCEPTIONS = sys.version_info < (3, 13)
141
141
142
142
143 def make_arrow(pad):
143 def make_arrow(pad):
144 """generate the leading arrow in front of traceback or debugger"""
144 """generate the leading arrow in front of traceback or debugger"""
145 if pad >= 2:
145 if pad >= 2:
146 return '-'*(pad-2) + '> '
146 return '-'*(pad-2) + '> '
147 elif pad == 1:
147 elif pad == 1:
148 return '>'
148 return '>'
149 return ''
149 return ''
150
150
151
151
152 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
152 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
153
153
154
154
155 def strip_indentation(multiline_string):
155 def strip_indentation(multiline_string):
156 return RGX_EXTRA_INDENT.sub('', multiline_string)
156 return RGX_EXTRA_INDENT.sub('', multiline_string)
157
157
158
158
159 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
159 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
160 """Make new_fn have old_fn's doc string. This is particularly useful
160 """Make new_fn have old_fn's doc string. This is particularly useful
161 for the ``do_...`` commands that hook into the help system.
161 for the ``do_...`` commands that hook into the help system.
162 Adapted from from a comp.lang.python posting
162 Adapted from from a comp.lang.python posting
163 by Duncan Booth."""
163 by Duncan Booth."""
164 def wrapper(*args, **kw):
164 def wrapper(*args, **kw):
165 return new_fn(*args, **kw)
165 return new_fn(*args, **kw)
166 if old_fn.__doc__:
166 if old_fn.__doc__:
167 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
167 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
168 return wrapper
168 return wrapper
169
169
170
170
171 class Pdb(OldPdb):
171 class Pdb(OldPdb):
172 """Modified Pdb class, does not load readline.
172 """Modified Pdb class, does not load readline.
173
173
174 for a standalone version that uses prompt_toolkit, see
174 for a standalone version that uses prompt_toolkit, see
175 `IPython.terminal.debugger.TerminalPdb` and
175 `IPython.terminal.debugger.TerminalPdb` and
176 `IPython.terminal.debugger.set_trace()`
176 `IPython.terminal.debugger.set_trace()`
177
177
178
178
179 This debugger can hide and skip frames that are tagged according to some predicates.
179 This debugger can hide and skip frames that are tagged according to some predicates.
180 See the `skip_predicates` commands.
180 See the `skip_predicates` commands.
181
181
182 """
182 """
183
183
184 shell: InteractiveShell
184 shell: InteractiveShell
185
185
186 if CHAIN_EXCEPTIONS:
186 if CHAIN_EXCEPTIONS:
187 MAX_CHAINED_EXCEPTION_DEPTH = 999
187 MAX_CHAINED_EXCEPTION_DEPTH = 999
188
188
189 default_predicates = {
189 default_predicates = {
190 "tbhide": True,
190 "tbhide": True,
191 "readonly": False,
191 "readonly": False,
192 "ipython_internal": True,
192 "ipython_internal": True,
193 "debuggerskip": True,
193 "debuggerskip": True,
194 }
194 }
195
195
196 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
196 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
197 """Create a new IPython debugger.
197 """Create a new IPython debugger.
198
198
199 Parameters
199 Parameters
200 ----------
200 ----------
201 completekey : default None
201 completekey : default None
202 Passed to pdb.Pdb.
202 Passed to pdb.Pdb.
203 stdin : default None
203 stdin : default None
204 Passed to pdb.Pdb.
204 Passed to pdb.Pdb.
205 stdout : default None
205 stdout : default None
206 Passed to pdb.Pdb.
206 Passed to pdb.Pdb.
207 context : int
207 context : int
208 Number of lines of source code context to show when
208 Number of lines of source code context to show when
209 displaying stacktrace information.
209 displaying stacktrace information.
210 **kwargs
210 **kwargs
211 Passed to pdb.Pdb.
211 Passed to pdb.Pdb.
212
212
213 Notes
213 Notes
214 -----
214 -----
215 The possibilities are python version dependent, see the python
215 The possibilities are python version dependent, see the python
216 docs for more info.
216 docs for more info.
217 """
217 """
218
218
219 # Parent constructor:
219 # Parent constructor:
220 try:
220 try:
221 self.context = int(context)
221 self.context = int(context)
222 if self.context <= 0:
222 if self.context <= 0:
223 raise ValueError("Context must be a positive integer")
223 raise ValueError("Context must be a positive integer")
224 except (TypeError, ValueError) as e:
224 except (TypeError, ValueError) as e:
225 raise ValueError("Context must be a positive integer") from e
225 raise ValueError("Context must be a positive integer") from e
226
226
227 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
227 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
228 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
228 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
229
229
230 # IPython changes...
230 # IPython changes...
231 self.shell = get_ipython()
231 self.shell = get_ipython()
232
232
233 if self.shell is None:
233 if self.shell is None:
234 save_main = sys.modules['__main__']
234 save_main = sys.modules['__main__']
235 # No IPython instance running, we must create one
235 # No IPython instance running, we must create one
236 from IPython.terminal.interactiveshell import \
236 from IPython.terminal.interactiveshell import \
237 TerminalInteractiveShell
237 TerminalInteractiveShell
238 self.shell = TerminalInteractiveShell.instance()
238 self.shell = TerminalInteractiveShell.instance()
239 # needed by any code which calls __import__("__main__") after
239 # needed by any code which calls __import__("__main__") after
240 # the debugger was entered. See also #9941.
240 # the debugger was entered. See also #9941.
241 sys.modules["__main__"] = save_main
241 sys.modules["__main__"] = save_main
242
242
243
243
244 color_scheme = self.shell.colors
244 color_scheme = self.shell.colors
245
245
246 self.aliases = {}
246 self.aliases = {}
247
247
248 # Create color table: we copy the default one from the traceback
248 # Create color table: we copy the default one from the traceback
249 # module and add a few attributes needed for debugging
249 # module and add a few attributes needed for debugging
250 self.color_scheme_table = exception_colors()
250 self.color_scheme_table = exception_colors()
251
251
252 # shorthands
252 # shorthands
253 C = coloransi.TermColors
253 C = coloransi.TermColors
254 cst = self.color_scheme_table
254 cst = self.color_scheme_table
255
255
256 cst['NoColor'].colors.prompt = C.NoColor
257 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
258 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
259
260 cst['Linux'].colors.prompt = C.Green
261 cst['Linux'].colors.breakpoint_enabled = C.LightRed
262 cst['Linux'].colors.breakpoint_disabled = C.Red
263
264 cst['LightBG'].colors.prompt = C.Blue
265 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
266 cst['LightBG'].colors.breakpoint_disabled = C.Red
267
268 cst['Neutral'].colors.prompt = C.Blue
269 cst['Neutral'].colors.breakpoint_enabled = C.LightRed
270 cst['Neutral'].colors.breakpoint_disabled = C.Red
271
256
272 # Add a python parser so we can syntax highlight source while
257 # Add a python parser so we can syntax highlight source while
273 # debugging.
258 # debugging.
274 self.parser = PyColorize.Parser(style=color_scheme)
259 self.parser = PyColorize.Parser(style=color_scheme)
275 self.set_colors(color_scheme)
260 self.set_colors(color_scheme)
276
261
277 # Set the prompt - the default prompt is '(Pdb)'
262 # Set the prompt - the default prompt is '(Pdb)'
278 self.prompt = prompt
263 self.prompt = prompt
279 self.skip_hidden = True
264 self.skip_hidden = True
280 self.report_skipped = True
265 self.report_skipped = True
281
266
282 # list of predicates we use to skip frames
267 # list of predicates we use to skip frames
283 self._predicates = self.default_predicates
268 self._predicates = self.default_predicates
284
269
285 if CHAIN_EXCEPTIONS:
270 if CHAIN_EXCEPTIONS:
286 self._chained_exceptions = tuple()
271 self._chained_exceptions = tuple()
287 self._chained_exception_index = 0
272 self._chained_exception_index = 0
288
273
289 #
274 #
290 def set_colors(self, scheme):
275 def set_colors(self, scheme):
291 """Shorthand access to the color table scheme selector method."""
276 """Shorthand access to the color table scheme selector method."""
292 self.color_scheme_table.set_active_scheme(scheme)
277 self.color_scheme_table.set_active_scheme(scheme)
293 self.parser.style = scheme
278 self.parser.style = scheme
294
279
295 def set_trace(self, frame=None):
280 def set_trace(self, frame=None):
296 if frame is None:
281 if frame is None:
297 frame = sys._getframe().f_back
282 frame = sys._getframe().f_back
298 self.initial_frame = frame
283 self.initial_frame = frame
299 return super().set_trace(frame)
284 return super().set_trace(frame)
300
285
301 def _hidden_predicate(self, frame):
286 def _hidden_predicate(self, frame):
302 """
287 """
303 Given a frame return whether it it should be hidden or not by IPython.
288 Given a frame return whether it it should be hidden or not by IPython.
304 """
289 """
305
290
306 if self._predicates["readonly"]:
291 if self._predicates["readonly"]:
307 fname = frame.f_code.co_filename
292 fname = frame.f_code.co_filename
308 # we need to check for file existence and interactively define
293 # we need to check for file existence and interactively define
309 # function would otherwise appear as RO.
294 # function would otherwise appear as RO.
310 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
295 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
311 return True
296 return True
312
297
313 if self._predicates["tbhide"]:
298 if self._predicates["tbhide"]:
314 if frame in (self.curframe, getattr(self, "initial_frame", None)):
299 if frame in (self.curframe, getattr(self, "initial_frame", None)):
315 return False
300 return False
316 frame_locals = self._get_frame_locals(frame)
301 frame_locals = self._get_frame_locals(frame)
317 if "__tracebackhide__" not in frame_locals:
302 if "__tracebackhide__" not in frame_locals:
318 return False
303 return False
319 return frame_locals["__tracebackhide__"]
304 return frame_locals["__tracebackhide__"]
320 return False
305 return False
321
306
322 def hidden_frames(self, stack):
307 def hidden_frames(self, stack):
323 """
308 """
324 Given an index in the stack return whether it should be skipped.
309 Given an index in the stack return whether it should be skipped.
325
310
326 This is used in up/down and where to skip frames.
311 This is used in up/down and where to skip frames.
327 """
312 """
328 # The f_locals dictionary is updated from the actual frame
313 # The f_locals dictionary is updated from the actual frame
329 # locals whenever the .f_locals accessor is called, so we
314 # locals whenever the .f_locals accessor is called, so we
330 # avoid calling it here to preserve self.curframe_locals.
315 # avoid calling it here to preserve self.curframe_locals.
331 # Furthermore, there is no good reason to hide the current frame.
316 # Furthermore, there is no good reason to hide the current frame.
332 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
317 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
333 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
318 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
334 if ip_start and self._predicates["ipython_internal"]:
319 if ip_start and self._predicates["ipython_internal"]:
335 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
320 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
336 return ip_hide
321 return ip_hide
337
322
338 if CHAIN_EXCEPTIONS:
323 if CHAIN_EXCEPTIONS:
339
324
340 def _get_tb_and_exceptions(self, tb_or_exc):
325 def _get_tb_and_exceptions(self, tb_or_exc):
341 """
326 """
342 Given a tracecack or an exception, return a tuple of chained exceptions
327 Given a tracecack or an exception, return a tuple of chained exceptions
343 and current traceback to inspect.
328 and current traceback to inspect.
344 This will deal with selecting the right ``__cause__`` or ``__context__``
329 This will deal with selecting the right ``__cause__`` or ``__context__``
345 as well as handling cycles, and return a flattened list of exceptions we
330 as well as handling cycles, and return a flattened list of exceptions we
346 can jump to with do_exceptions.
331 can jump to with do_exceptions.
347 """
332 """
348 _exceptions = []
333 _exceptions = []
349 if isinstance(tb_or_exc, BaseException):
334 if isinstance(tb_or_exc, BaseException):
350 traceback, current = tb_or_exc.__traceback__, tb_or_exc
335 traceback, current = tb_or_exc.__traceback__, tb_or_exc
351
336
352 while current is not None:
337 while current is not None:
353 if current in _exceptions:
338 if current in _exceptions:
354 break
339 break
355 _exceptions.append(current)
340 _exceptions.append(current)
356 if current.__cause__ is not None:
341 if current.__cause__ is not None:
357 current = current.__cause__
342 current = current.__cause__
358 elif (
343 elif (
359 current.__context__ is not None
344 current.__context__ is not None
360 and not current.__suppress_context__
345 and not current.__suppress_context__
361 ):
346 ):
362 current = current.__context__
347 current = current.__context__
363
348
364 if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
349 if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
365 self.message(
350 self.message(
366 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
351 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
367 " chained exceptions found, not all exceptions"
352 " chained exceptions found, not all exceptions"
368 "will be browsable with `exceptions`."
353 "will be browsable with `exceptions`."
369 )
354 )
370 break
355 break
371 else:
356 else:
372 traceback = tb_or_exc
357 traceback = tb_or_exc
373 return tuple(reversed(_exceptions)), traceback
358 return tuple(reversed(_exceptions)), traceback
374
359
375 @contextmanager
360 @contextmanager
376 def _hold_exceptions(self, exceptions):
361 def _hold_exceptions(self, exceptions):
377 """
362 """
378 Context manager to ensure proper cleaning of exceptions references
363 Context manager to ensure proper cleaning of exceptions references
379 When given a chained exception instead of a traceback,
364 When given a chained exception instead of a traceback,
380 pdb may hold references to many objects which may leak memory.
365 pdb may hold references to many objects which may leak memory.
381 We use this context manager to make sure everything is properly cleaned
366 We use this context manager to make sure everything is properly cleaned
382 """
367 """
383 try:
368 try:
384 self._chained_exceptions = exceptions
369 self._chained_exceptions = exceptions
385 self._chained_exception_index = len(exceptions) - 1
370 self._chained_exception_index = len(exceptions) - 1
386 yield
371 yield
387 finally:
372 finally:
388 # we can't put those in forget as otherwise they would
373 # we can't put those in forget as otherwise they would
389 # be cleared on exception change
374 # be cleared on exception change
390 self._chained_exceptions = tuple()
375 self._chained_exceptions = tuple()
391 self._chained_exception_index = 0
376 self._chained_exception_index = 0
392
377
393 def do_exceptions(self, arg):
378 def do_exceptions(self, arg):
394 """exceptions [number]
379 """exceptions [number]
395 List or change current exception in an exception chain.
380 List or change current exception in an exception chain.
396 Without arguments, list all the current exception in the exception
381 Without arguments, list all the current exception in the exception
397 chain. Exceptions will be numbered, with the current exception indicated
382 chain. Exceptions will be numbered, with the current exception indicated
398 with an arrow.
383 with an arrow.
399 If given an integer as argument, switch to the exception at that index.
384 If given an integer as argument, switch to the exception at that index.
400 """
385 """
401 if not self._chained_exceptions:
386 if not self._chained_exceptions:
402 self.message(
387 self.message(
403 "Did not find chained exceptions. To move between"
388 "Did not find chained exceptions. To move between"
404 " exceptions, pdb/post_mortem must be given an exception"
389 " exceptions, pdb/post_mortem must be given an exception"
405 " object rather than a traceback."
390 " object rather than a traceback."
406 )
391 )
407 return
392 return
408 if not arg:
393 if not arg:
409 for ix, exc in enumerate(self._chained_exceptions):
394 for ix, exc in enumerate(self._chained_exceptions):
410 prompt = ">" if ix == self._chained_exception_index else " "
395 prompt = ">" if ix == self._chained_exception_index else " "
411 rep = repr(exc)
396 rep = repr(exc)
412 if len(rep) > 80:
397 if len(rep) > 80:
413 rep = rep[:77] + "..."
398 rep = rep[:77] + "..."
414 indicator = (
399 indicator = (
415 " -"
400 " -"
416 if self._chained_exceptions[ix].__traceback__ is None
401 if self._chained_exceptions[ix].__traceback__ is None
417 else f"{ix:>3}"
402 else f"{ix:>3}"
418 )
403 )
419 self.message(f"{prompt} {indicator} {rep}")
404 self.message(f"{prompt} {indicator} {rep}")
420 else:
405 else:
421 try:
406 try:
422 number = int(arg)
407 number = int(arg)
423 except ValueError:
408 except ValueError:
424 self.error("Argument must be an integer")
409 self.error("Argument must be an integer")
425 return
410 return
426 if 0 <= number < len(self._chained_exceptions):
411 if 0 <= number < len(self._chained_exceptions):
427 if self._chained_exceptions[number].__traceback__ is None:
412 if self._chained_exceptions[number].__traceback__ is None:
428 self.error(
413 self.error(
429 "This exception does not have a traceback, cannot jump to it"
414 "This exception does not have a traceback, cannot jump to it"
430 )
415 )
431 return
416 return
432
417
433 self._chained_exception_index = number
418 self._chained_exception_index = number
434 self.setup(None, self._chained_exceptions[number].__traceback__)
419 self.setup(None, self._chained_exceptions[number].__traceback__)
435 self.print_stack_entry(self.stack[self.curindex])
420 self.print_stack_entry(self.stack[self.curindex])
436 else:
421 else:
437 self.error("No exception with that number")
422 self.error("No exception with that number")
438
423
439 def interaction(self, frame, tb_or_exc):
424 def interaction(self, frame, tb_or_exc):
440 try:
425 try:
441 if CHAIN_EXCEPTIONS:
426 if CHAIN_EXCEPTIONS:
442 # this context manager is part of interaction in 3.13
427 # this context manager is part of interaction in 3.13
443 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
428 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
444 if isinstance(tb_or_exc, BaseException):
429 if isinstance(tb_or_exc, BaseException):
445 assert tb is not None, "main exception must have a traceback"
430 assert tb is not None, "main exception must have a traceback"
446 with self._hold_exceptions(_chained_exceptions):
431 with self._hold_exceptions(_chained_exceptions):
447 OldPdb.interaction(self, frame, tb)
432 OldPdb.interaction(self, frame, tb)
448 else:
433 else:
449 OldPdb.interaction(self, frame, tb_or_exc)
434 OldPdb.interaction(self, frame, tb_or_exc)
450
435
451 except KeyboardInterrupt:
436 except KeyboardInterrupt:
452 self.stdout.write("\n" + self.shell.get_exception_only())
437 self.stdout.write("\n" + self.shell.get_exception_only())
453
438
454 def precmd(self, line):
439 def precmd(self, line):
455 """Perform useful escapes on the command before it is executed."""
440 """Perform useful escapes on the command before it is executed."""
456
441
457 if line.endswith("??"):
442 if line.endswith("??"):
458 line = "pinfo2 " + line[:-2]
443 line = "pinfo2 " + line[:-2]
459 elif line.endswith("?"):
444 elif line.endswith("?"):
460 line = "pinfo " + line[:-1]
445 line = "pinfo " + line[:-1]
461
446
462 line = super().precmd(line)
447 line = super().precmd(line)
463
448
464 return line
449 return line
465
450
466 def new_do_quit(self, arg):
451 def new_do_quit(self, arg):
467 return OldPdb.do_quit(self, arg)
452 return OldPdb.do_quit(self, arg)
468
453
469 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
454 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
470
455
471 def print_stack_trace(self, context=None):
456 def print_stack_trace(self, context=None):
472 Colors = self.color_scheme_table.active_colors
457 Colors = self.color_scheme_table.active_colors
473 ColorsNormal = Colors.Normal
458 ColorsNormal = Colors.Normal
474 if context is None:
459 if context is None:
475 context = self.context
460 context = self.context
476 try:
461 try:
477 context = int(context)
462 context = int(context)
478 if context <= 0:
463 if context <= 0:
479 raise ValueError("Context must be a positive integer")
464 raise ValueError("Context must be a positive integer")
480 except (TypeError, ValueError) as e:
465 except (TypeError, ValueError) as e:
481 raise ValueError("Context must be a positive integer") from e
466 raise ValueError("Context must be a positive integer") from e
482 try:
467 try:
483 skipped = 0
468 skipped = 0
484 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
469 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
485 if hidden and self.skip_hidden:
470 if hidden and self.skip_hidden:
486 skipped += 1
471 skipped += 1
487 continue
472 continue
488 if skipped:
473 if skipped:
489 print(
474 print(
490 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
475 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
491 )
476 )
492 skipped = 0
477 skipped = 0
493 self.print_stack_entry(frame_lineno, context=context)
478 self.print_stack_entry(frame_lineno, context=context)
494 if skipped:
479 if skipped:
495 print(
480 print(
496 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
481 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
497 )
482 )
498 except KeyboardInterrupt:
483 except KeyboardInterrupt:
499 pass
484 pass
500
485
501 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
486 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
502 context=None):
487 context=None):
503 if context is None:
488 if context is None:
504 context = self.context
489 context = self.context
505 try:
490 try:
506 context = int(context)
491 context = int(context)
507 if context <= 0:
492 if context <= 0:
508 raise ValueError("Context must be a positive integer")
493 raise ValueError("Context must be a positive integer")
509 except (TypeError, ValueError) as e:
494 except (TypeError, ValueError) as e:
510 raise ValueError("Context must be a positive integer") from e
495 raise ValueError("Context must be a positive integer") from e
511 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
496 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
512
497
513 # vds: >>
498 # vds: >>
514 frame, lineno = frame_lineno
499 frame, lineno = frame_lineno
515 filename = frame.f_code.co_filename
500 filename = frame.f_code.co_filename
516 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
501 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
517 # vds: <<
502 # vds: <<
518
503
519 def _get_frame_locals(self, frame):
504 def _get_frame_locals(self, frame):
520 """ "
505 """ "
521 Accessing f_local of current frame reset the namespace, so we want to avoid
506 Accessing f_local of current frame reset the namespace, so we want to avoid
522 that or the following can happen
507 that or the following can happen
523
508
524 ipdb> foo
509 ipdb> foo
525 "old"
510 "old"
526 ipdb> foo = "new"
511 ipdb> foo = "new"
527 ipdb> foo
512 ipdb> foo
528 "new"
513 "new"
529 ipdb> where
514 ipdb> where
530 ipdb> foo
515 ipdb> foo
531 "old"
516 "old"
532
517
533 So if frame is self.current_frame we instead return self.curframe_locals
518 So if frame is self.current_frame we instead return self.curframe_locals
534
519
535 """
520 """
536 if frame is self.curframe:
521 if frame is self.curframe:
537 return self.curframe_locals
522 return self.curframe_locals
538 else:
523 else:
539 return frame.f_locals
524 return frame.f_locals
540
525
541 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
526 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
542 if context is None:
527 if context is None:
543 context = self.context
528 context = self.context
544 try:
529 try:
545 context = int(context)
530 context = int(context)
546 if context <= 0:
531 if context <= 0:
547 print("Context must be a positive integer", file=self.stdout)
532 print("Context must be a positive integer", file=self.stdout)
548 except (TypeError, ValueError):
533 except (TypeError, ValueError):
549 print("Context must be a positive integer", file=self.stdout)
534 print("Context must be a positive integer", file=self.stdout)
550
535
551 import reprlib
536 import reprlib
552
537
553 ret = []
538 ret = []
554
539
555 Colors = self.color_scheme_table.active_colors
540 Colors = self.color_scheme_table.active_colors
556 ColorsNormal = Colors.Normal
541 ColorsNormal = Colors.Normal
557 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
542 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
558 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
543 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
559 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
544 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
560 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
545 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
561
546
562 frame, lineno = frame_lineno
547 frame, lineno = frame_lineno
563
548
564 return_value = ''
549 return_value = ''
565 loc_frame = self._get_frame_locals(frame)
550 loc_frame = self._get_frame_locals(frame)
566 if "__return__" in loc_frame:
551 if "__return__" in loc_frame:
567 rv = loc_frame["__return__"]
552 rv = loc_frame["__return__"]
568 # return_value += '->'
553 # return_value += '->'
569 return_value += reprlib.repr(rv) + "\n"
554 return_value += reprlib.repr(rv) + "\n"
570 ret.append(return_value)
555 ret.append(return_value)
571
556
572 #s = filename + '(' + `lineno` + ')'
557 #s = filename + '(' + `lineno` + ')'
573 filename = self.canonic(frame.f_code.co_filename)
558 filename = self.canonic(frame.f_code.co_filename)
574 link = tpl_link % py3compat.cast_unicode(filename)
559 link = tpl_link % py3compat.cast_unicode(filename)
575
560
576 if frame.f_code.co_name:
561 if frame.f_code.co_name:
577 func = frame.f_code.co_name
562 func = frame.f_code.co_name
578 else:
563 else:
579 func = "<lambda>"
564 func = "<lambda>"
580
565
581 call = ""
566 call = ""
582 if func != "?":
567 if func != "?":
583 if "__args__" in loc_frame:
568 if "__args__" in loc_frame:
584 args = reprlib.repr(loc_frame["__args__"])
569 args = reprlib.repr(loc_frame["__args__"])
585 else:
570 else:
586 args = '()'
571 args = '()'
587 call = tpl_call % (func, args)
572 call = tpl_call % (func, args)
588
573
589 # The level info should be generated in the same format pdb uses, to
574 # The level info should be generated in the same format pdb uses, to
590 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
575 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
591 if frame is self.curframe:
576 if frame is self.curframe:
592 ret.append('> ')
577 ret.append('> ')
593 else:
578 else:
594 ret.append(" ")
579 ret.append(" ")
595 ret.append("%s(%s)%s\n" % (link, lineno, call))
580 ret.append("%s(%s)%s\n" % (link, lineno, call))
596
581
597 start = lineno - 1 - context//2
582 start = lineno - 1 - context//2
598 lines = linecache.getlines(filename)
583 lines = linecache.getlines(filename)
599 start = min(start, len(lines) - context)
584 start = min(start, len(lines) - context)
600 start = max(start, 0)
585 start = max(start, 0)
601 lines = lines[start : start + context]
586 lines = lines[start : start + context]
602
587
603 for i, line in enumerate(lines):
588 for i, line in enumerate(lines):
604 show_arrow = start + 1 + i == lineno
589 show_arrow = start + 1 + i == lineno
605 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
590 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
606 ret.append(
591 ret.append(
607 self.__format_line(
592 self.__format_line(
608 linetpl, filename, start + 1 + i, line, arrow=show_arrow
593 linetpl, filename, start + 1 + i, line, arrow=show_arrow
609 )
594 )
610 )
595 )
611 return "".join(ret)
596 return "".join(ret)
612
597
613 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
598 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
614 bp_mark = ""
599 bp_mark = ""
615 bp_mark_color = ""
600 bp_mark_color = ""
616
601
617 new_line, err = self.parser.format2(line, 'str')
602 new_line, err = self.parser.format2(line, 'str')
618 if not err:
603 if not err:
619 line = new_line
604 line = new_line
620
605
621 bp = None
606 bp = None
622 if lineno in self.get_file_breaks(filename):
607 if lineno in self.get_file_breaks(filename):
623 bps = self.get_breaks(filename, lineno)
608 bps = self.get_breaks(filename, lineno)
624 bp = bps[-1]
609 bp = bps[-1]
625
610
626 if bp:
611 if bp:
627 Colors = self.color_scheme_table.active_colors
612 Colors = self.color_scheme_table.active_colors
628 bp_mark = str(bp.number)
613 bp_mark = str(bp.number)
629 bp_mark_color = Colors.breakpoint_enabled
614 bp_mark_color = Colors.breakpoint_enabled
630 if not bp.enabled:
615 if not bp.enabled:
631 bp_mark_color = Colors.breakpoint_disabled
616 bp_mark_color = Colors.breakpoint_disabled
632
617
633 numbers_width = 7
618 numbers_width = 7
634 if arrow:
619 if arrow:
635 # This is the line with the error
620 # This is the line with the error
636 pad = numbers_width - len(str(lineno)) - len(bp_mark)
621 pad = numbers_width - len(str(lineno)) - len(bp_mark)
637 num = '%s%s' % (make_arrow(pad), str(lineno))
622 num = '%s%s' % (make_arrow(pad), str(lineno))
638 else:
623 else:
639 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
624 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
640
625
641 return tpl_line % (bp_mark_color + bp_mark, num, line)
626 return tpl_line % (bp_mark_color + bp_mark, num, line)
642
627
643 def print_list_lines(self, filename, first, last):
628 def print_list_lines(self, filename, first, last):
644 """The printing (as opposed to the parsing part of a 'list'
629 """The printing (as opposed to the parsing part of a 'list'
645 command."""
630 command."""
646 try:
631 try:
647 Colors = self.color_scheme_table.active_colors
632 Colors = self.color_scheme_table.active_colors
648 ColorsNormal = Colors.Normal
633 ColorsNormal = Colors.Normal
649 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
634 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
650 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
635 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
651 src = []
636 src = []
652 if filename == "<string>" and hasattr(self, "_exec_filename"):
637 if filename == "<string>" and hasattr(self, "_exec_filename"):
653 filename = self._exec_filename
638 filename = self._exec_filename
654
639
655 for lineno in range(first, last+1):
640 for lineno in range(first, last+1):
656 line = linecache.getline(filename, lineno)
641 line = linecache.getline(filename, lineno)
657 if not line:
642 if not line:
658 break
643 break
659
644
660 if lineno == self.curframe.f_lineno:
645 if lineno == self.curframe.f_lineno:
661 line = self.__format_line(
646 line = self.__format_line(
662 tpl_line_em, filename, lineno, line, arrow=True
647 tpl_line_em, filename, lineno, line, arrow=True
663 )
648 )
664 else:
649 else:
665 line = self.__format_line(
650 line = self.__format_line(
666 tpl_line, filename, lineno, line, arrow=False
651 tpl_line, filename, lineno, line, arrow=False
667 )
652 )
668
653
669 src.append(line)
654 src.append(line)
670 self.lineno = lineno
655 self.lineno = lineno
671
656
672 print(''.join(src), file=self.stdout)
657 print(''.join(src), file=self.stdout)
673
658
674 except KeyboardInterrupt:
659 except KeyboardInterrupt:
675 pass
660 pass
676
661
677 def do_skip_predicates(self, args):
662 def do_skip_predicates(self, args):
678 """
663 """
679 Turn on/off individual predicates as to whether a frame should be hidden/skip.
664 Turn on/off individual predicates as to whether a frame should be hidden/skip.
680
665
681 The global option to skip (or not) hidden frames is set with skip_hidden
666 The global option to skip (or not) hidden frames is set with skip_hidden
682
667
683 To change the value of a predicate
668 To change the value of a predicate
684
669
685 skip_predicates key [true|false]
670 skip_predicates key [true|false]
686
671
687 Call without arguments to see the current values.
672 Call without arguments to see the current values.
688
673
689 To permanently change the value of an option add the corresponding
674 To permanently change the value of an option add the corresponding
690 command to your ``~/.pdbrc`` file. If you are programmatically using the
675 command to your ``~/.pdbrc`` file. If you are programmatically using the
691 Pdb instance you can also change the ``default_predicates`` class
676 Pdb instance you can also change the ``default_predicates`` class
692 attribute.
677 attribute.
693 """
678 """
694 if not args.strip():
679 if not args.strip():
695 print("current predicates:")
680 print("current predicates:")
696 for p, v in self._predicates.items():
681 for p, v in self._predicates.items():
697 print(" ", p, ":", v)
682 print(" ", p, ":", v)
698 return
683 return
699 type_value = args.strip().split(" ")
684 type_value = args.strip().split(" ")
700 if len(type_value) != 2:
685 if len(type_value) != 2:
701 print(
686 print(
702 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
687 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
703 )
688 )
704 return
689 return
705
690
706 type_, value = type_value
691 type_, value = type_value
707 if type_ not in self._predicates:
692 if type_ not in self._predicates:
708 print(f"{type_!r} not in {set(self._predicates.keys())}")
693 print(f"{type_!r} not in {set(self._predicates.keys())}")
709 return
694 return
710 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
695 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
711 print(
696 print(
712 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
697 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
713 )
698 )
714 return
699 return
715
700
716 self._predicates[type_] = value.lower() in ("true", "yes", "1")
701 self._predicates[type_] = value.lower() in ("true", "yes", "1")
717 if not any(self._predicates.values()):
702 if not any(self._predicates.values()):
718 print(
703 print(
719 "Warning, all predicates set to False, skip_hidden may not have any effects."
704 "Warning, all predicates set to False, skip_hidden may not have any effects."
720 )
705 )
721
706
722 def do_skip_hidden(self, arg):
707 def do_skip_hidden(self, arg):
723 """
708 """
724 Change whether or not we should skip frames with the
709 Change whether or not we should skip frames with the
725 __tracebackhide__ attribute.
710 __tracebackhide__ attribute.
726 """
711 """
727 if not arg.strip():
712 if not arg.strip():
728 print(
713 print(
729 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
714 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
730 )
715 )
731 elif arg.strip().lower() in ("true", "yes"):
716 elif arg.strip().lower() in ("true", "yes"):
732 self.skip_hidden = True
717 self.skip_hidden = True
733 elif arg.strip().lower() in ("false", "no"):
718 elif arg.strip().lower() in ("false", "no"):
734 self.skip_hidden = False
719 self.skip_hidden = False
735 if not any(self._predicates.values()):
720 if not any(self._predicates.values()):
736 print(
721 print(
737 "Warning, all predicates set to False, skip_hidden may not have any effects."
722 "Warning, all predicates set to False, skip_hidden may not have any effects."
738 )
723 )
739
724
740 def do_list(self, arg):
725 def do_list(self, arg):
741 """Print lines of code from the current stack frame
726 """Print lines of code from the current stack frame
742 """
727 """
743 self.lastcmd = 'list'
728 self.lastcmd = 'list'
744 last = None
729 last = None
745 if arg and arg != ".":
730 if arg and arg != ".":
746 try:
731 try:
747 x = eval(arg, {}, {})
732 x = eval(arg, {}, {})
748 if type(x) == type(()):
733 if type(x) == type(()):
749 first, last = x
734 first, last = x
750 first = int(first)
735 first = int(first)
751 last = int(last)
736 last = int(last)
752 if last < first:
737 if last < first:
753 # Assume it's a count
738 # Assume it's a count
754 last = first + last
739 last = first + last
755 else:
740 else:
756 first = max(1, int(x) - 5)
741 first = max(1, int(x) - 5)
757 except:
742 except:
758 print('*** Error in argument:', repr(arg), file=self.stdout)
743 print('*** Error in argument:', repr(arg), file=self.stdout)
759 return
744 return
760 elif self.lineno is None or arg == ".":
745 elif self.lineno is None or arg == ".":
761 first = max(1, self.curframe.f_lineno - 5)
746 first = max(1, self.curframe.f_lineno - 5)
762 else:
747 else:
763 first = self.lineno + 1
748 first = self.lineno + 1
764 if last is None:
749 if last is None:
765 last = first + 10
750 last = first + 10
766 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
751 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
767
752
768 # vds: >>
753 # vds: >>
769 lineno = first
754 lineno = first
770 filename = self.curframe.f_code.co_filename
755 filename = self.curframe.f_code.co_filename
771 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
756 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
772 # vds: <<
757 # vds: <<
773
758
774 do_l = do_list
759 do_l = do_list
775
760
776 def getsourcelines(self, obj):
761 def getsourcelines(self, obj):
777 lines, lineno = inspect.findsource(obj)
762 lines, lineno = inspect.findsource(obj)
778 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
763 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
779 # must be a module frame: do not try to cut a block out of it
764 # must be a module frame: do not try to cut a block out of it
780 return lines, 1
765 return lines, 1
781 elif inspect.ismodule(obj):
766 elif inspect.ismodule(obj):
782 return lines, 1
767 return lines, 1
783 return inspect.getblock(lines[lineno:]), lineno+1
768 return inspect.getblock(lines[lineno:]), lineno+1
784
769
785 def do_longlist(self, arg):
770 def do_longlist(self, arg):
786 """Print lines of code from the current stack frame.
771 """Print lines of code from the current stack frame.
787
772
788 Shows more lines than 'list' does.
773 Shows more lines than 'list' does.
789 """
774 """
790 self.lastcmd = 'longlist'
775 self.lastcmd = 'longlist'
791 try:
776 try:
792 lines, lineno = self.getsourcelines(self.curframe)
777 lines, lineno = self.getsourcelines(self.curframe)
793 except OSError as err:
778 except OSError as err:
794 self.error(err)
779 self.error(err)
795 return
780 return
796 last = lineno + len(lines)
781 last = lineno + len(lines)
797 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
782 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
798 do_ll = do_longlist
783 do_ll = do_longlist
799
784
800 def do_debug(self, arg):
785 def do_debug(self, arg):
801 """debug code
786 """debug code
802 Enter a recursive debugger that steps through the code
787 Enter a recursive debugger that steps through the code
803 argument (which is an arbitrary expression or statement to be
788 argument (which is an arbitrary expression or statement to be
804 executed in the current environment).
789 executed in the current environment).
805 """
790 """
806 trace_function = sys.gettrace()
791 trace_function = sys.gettrace()
807 sys.settrace(None)
792 sys.settrace(None)
808 globals = self.curframe.f_globals
793 globals = self.curframe.f_globals
809 locals = self.curframe_locals
794 locals = self.curframe_locals
810 p = self.__class__(completekey=self.completekey,
795 p = self.__class__(completekey=self.completekey,
811 stdin=self.stdin, stdout=self.stdout)
796 stdin=self.stdin, stdout=self.stdout)
812 p.use_rawinput = self.use_rawinput
797 p.use_rawinput = self.use_rawinput
813 p.prompt = "(%s) " % self.prompt.strip()
798 p.prompt = "(%s) " % self.prompt.strip()
814 self.message("ENTERING RECURSIVE DEBUGGER")
799 self.message("ENTERING RECURSIVE DEBUGGER")
815 sys.call_tracing(p.run, (arg, globals, locals))
800 sys.call_tracing(p.run, (arg, globals, locals))
816 self.message("LEAVING RECURSIVE DEBUGGER")
801 self.message("LEAVING RECURSIVE DEBUGGER")
817 sys.settrace(trace_function)
802 sys.settrace(trace_function)
818 self.lastcmd = p.lastcmd
803 self.lastcmd = p.lastcmd
819
804
820 def do_pdef(self, arg):
805 def do_pdef(self, arg):
821 """Print the call signature for any callable object.
806 """Print the call signature for any callable object.
822
807
823 The debugger interface to %pdef"""
808 The debugger interface to %pdef"""
824 namespaces = [
809 namespaces = [
825 ("Locals", self.curframe_locals),
810 ("Locals", self.curframe_locals),
826 ("Globals", self.curframe.f_globals),
811 ("Globals", self.curframe.f_globals),
827 ]
812 ]
828 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
813 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
829
814
830 def do_pdoc(self, arg):
815 def do_pdoc(self, arg):
831 """Print the docstring for an object.
816 """Print the docstring for an object.
832
817
833 The debugger interface to %pdoc."""
818 The debugger interface to %pdoc."""
834 namespaces = [
819 namespaces = [
835 ("Locals", self.curframe_locals),
820 ("Locals", self.curframe_locals),
836 ("Globals", self.curframe.f_globals),
821 ("Globals", self.curframe.f_globals),
837 ]
822 ]
838 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
823 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
839
824
840 def do_pfile(self, arg):
825 def do_pfile(self, arg):
841 """Print (or run through pager) the file where an object is defined.
826 """Print (or run through pager) the file where an object is defined.
842
827
843 The debugger interface to %pfile.
828 The debugger interface to %pfile.
844 """
829 """
845 namespaces = [
830 namespaces = [
846 ("Locals", self.curframe_locals),
831 ("Locals", self.curframe_locals),
847 ("Globals", self.curframe.f_globals),
832 ("Globals", self.curframe.f_globals),
848 ]
833 ]
849 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
834 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
850
835
851 def do_pinfo(self, arg):
836 def do_pinfo(self, arg):
852 """Provide detailed information about an object.
837 """Provide detailed information about an object.
853
838
854 The debugger interface to %pinfo, i.e., obj?."""
839 The debugger interface to %pinfo, i.e., obj?."""
855 namespaces = [
840 namespaces = [
856 ("Locals", self.curframe_locals),
841 ("Locals", self.curframe_locals),
857 ("Globals", self.curframe.f_globals),
842 ("Globals", self.curframe.f_globals),
858 ]
843 ]
859 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
844 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
860
845
861 def do_pinfo2(self, arg):
846 def do_pinfo2(self, arg):
862 """Provide extra detailed information about an object.
847 """Provide extra detailed information about an object.
863
848
864 The debugger interface to %pinfo2, i.e., obj??."""
849 The debugger interface to %pinfo2, i.e., obj??."""
865 namespaces = [
850 namespaces = [
866 ("Locals", self.curframe_locals),
851 ("Locals", self.curframe_locals),
867 ("Globals", self.curframe.f_globals),
852 ("Globals", self.curframe.f_globals),
868 ]
853 ]
869 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
854 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
870
855
871 def do_psource(self, arg):
856 def do_psource(self, arg):
872 """Print (or run through pager) the source code for an object."""
857 """Print (or run through pager) the source code for an object."""
873 namespaces = [
858 namespaces = [
874 ("Locals", self.curframe_locals),
859 ("Locals", self.curframe_locals),
875 ("Globals", self.curframe.f_globals),
860 ("Globals", self.curframe.f_globals),
876 ]
861 ]
877 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
862 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
878
863
879 def do_where(self, arg):
864 def do_where(self, arg):
880 """w(here)
865 """w(here)
881 Print a stack trace, with the most recent frame at the bottom.
866 Print a stack trace, with the most recent frame at the bottom.
882 An arrow indicates the "current frame", which determines the
867 An arrow indicates the "current frame", which determines the
883 context of most commands. 'bt' is an alias for this command.
868 context of most commands. 'bt' is an alias for this command.
884
869
885 Take a number as argument as an (optional) number of context line to
870 Take a number as argument as an (optional) number of context line to
886 print"""
871 print"""
887 if arg:
872 if arg:
888 try:
873 try:
889 context = int(arg)
874 context = int(arg)
890 except ValueError as err:
875 except ValueError as err:
891 self.error(err)
876 self.error(err)
892 return
877 return
893 self.print_stack_trace(context)
878 self.print_stack_trace(context)
894 else:
879 else:
895 self.print_stack_trace()
880 self.print_stack_trace()
896
881
897 do_w = do_where
882 do_w = do_where
898
883
899 def break_anywhere(self, frame):
884 def break_anywhere(self, frame):
900 """
885 """
901 _stop_in_decorator_internals is overly restrictive, as we may still want
886 _stop_in_decorator_internals is overly restrictive, as we may still want
902 to trace function calls, so we need to also update break_anywhere so
887 to trace function calls, so we need to also update break_anywhere so
903 that is we don't `stop_here`, because of debugger skip, we may still
888 that is we don't `stop_here`, because of debugger skip, we may still
904 stop at any point inside the function
889 stop at any point inside the function
905
890
906 """
891 """
907
892
908 sup = super().break_anywhere(frame)
893 sup = super().break_anywhere(frame)
909 if sup:
894 if sup:
910 return sup
895 return sup
911 if self._predicates["debuggerskip"]:
896 if self._predicates["debuggerskip"]:
912 if DEBUGGERSKIP in frame.f_code.co_varnames:
897 if DEBUGGERSKIP in frame.f_code.co_varnames:
913 return True
898 return True
914 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
899 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
915 return True
900 return True
916 return False
901 return False
917
902
918 def _is_in_decorator_internal_and_should_skip(self, frame):
903 def _is_in_decorator_internal_and_should_skip(self, frame):
919 """
904 """
920 Utility to tell us whether we are in a decorator internal and should stop.
905 Utility to tell us whether we are in a decorator internal and should stop.
921
906
922 """
907 """
923 # if we are disabled don't skip
908 # if we are disabled don't skip
924 if not self._predicates["debuggerskip"]:
909 if not self._predicates["debuggerskip"]:
925 return False
910 return False
926
911
927 return self._cachable_skip(frame)
912 return self._cachable_skip(frame)
928
913
929 @lru_cache
914 @lru_cache
930 def _cachable_skip(self, frame):
915 def _cachable_skip(self, frame):
931 # if frame is tagged, skip by default.
916 # if frame is tagged, skip by default.
932 if DEBUGGERSKIP in frame.f_code.co_varnames:
917 if DEBUGGERSKIP in frame.f_code.co_varnames:
933 return True
918 return True
934
919
935 # if one of the parent frame value set to True skip as well.
920 # if one of the parent frame value set to True skip as well.
936
921
937 cframe = frame
922 cframe = frame
938 while getattr(cframe, "f_back", None):
923 while getattr(cframe, "f_back", None):
939 cframe = cframe.f_back
924 cframe = cframe.f_back
940 if self._get_frame_locals(cframe).get(DEBUGGERSKIP):
925 if self._get_frame_locals(cframe).get(DEBUGGERSKIP):
941 return True
926 return True
942
927
943 return False
928 return False
944
929
945 def stop_here(self, frame):
930 def stop_here(self, frame):
946 if self._is_in_decorator_internal_and_should_skip(frame) is True:
931 if self._is_in_decorator_internal_and_should_skip(frame) is True:
947 return False
932 return False
948
933
949 hidden = False
934 hidden = False
950 if self.skip_hidden:
935 if self.skip_hidden:
951 hidden = self._hidden_predicate(frame)
936 hidden = self._hidden_predicate(frame)
952 if hidden:
937 if hidden:
953 if self.report_skipped:
938 if self.report_skipped:
954 Colors = self.color_scheme_table.active_colors
939 Colors = self.color_scheme_table.active_colors
955 ColorsNormal = Colors.Normal
940 ColorsNormal = Colors.Normal
956 print(
941 print(
957 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
942 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
958 )
943 )
959 return super().stop_here(frame)
944 return super().stop_here(frame)
960
945
961 def do_up(self, arg):
946 def do_up(self, arg):
962 """u(p) [count]
947 """u(p) [count]
963 Move the current frame count (default one) levels up in the
948 Move the current frame count (default one) levels up in the
964 stack trace (to an older frame).
949 stack trace (to an older frame).
965
950
966 Will skip hidden frames.
951 Will skip hidden frames.
967 """
952 """
968 # modified version of upstream that skips
953 # modified version of upstream that skips
969 # frames with __tracebackhide__
954 # frames with __tracebackhide__
970 if self.curindex == 0:
955 if self.curindex == 0:
971 self.error("Oldest frame")
956 self.error("Oldest frame")
972 return
957 return
973 try:
958 try:
974 count = int(arg or 1)
959 count = int(arg or 1)
975 except ValueError:
960 except ValueError:
976 self.error("Invalid frame count (%s)" % arg)
961 self.error("Invalid frame count (%s)" % arg)
977 return
962 return
978 skipped = 0
963 skipped = 0
979 if count < 0:
964 if count < 0:
980 _newframe = 0
965 _newframe = 0
981 else:
966 else:
982 counter = 0
967 counter = 0
983 hidden_frames = self.hidden_frames(self.stack)
968 hidden_frames = self.hidden_frames(self.stack)
984 for i in range(self.curindex - 1, -1, -1):
969 for i in range(self.curindex - 1, -1, -1):
985 if hidden_frames[i] and self.skip_hidden:
970 if hidden_frames[i] and self.skip_hidden:
986 skipped += 1
971 skipped += 1
987 continue
972 continue
988 counter += 1
973 counter += 1
989 if counter >= count:
974 if counter >= count:
990 break
975 break
991 else:
976 else:
992 # if no break occurred.
977 # if no break occurred.
993 self.error(
978 self.error(
994 "all frames above hidden, use `skip_hidden False` to get get into those."
979 "all frames above hidden, use `skip_hidden False` to get get into those."
995 )
980 )
996 return
981 return
997
982
998 Colors = self.color_scheme_table.active_colors
983 Colors = self.color_scheme_table.active_colors
999 ColorsNormal = Colors.Normal
984 ColorsNormal = Colors.Normal
1000 _newframe = i
985 _newframe = i
1001 self._select_frame(_newframe)
986 self._select_frame(_newframe)
1002 if skipped:
987 if skipped:
1003 print(
988 print(
1004 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
989 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1005 )
990 )
1006
991
1007 def do_down(self, arg):
992 def do_down(self, arg):
1008 """d(own) [count]
993 """d(own) [count]
1009 Move the current frame count (default one) levels down in the
994 Move the current frame count (default one) levels down in the
1010 stack trace (to a newer frame).
995 stack trace (to a newer frame).
1011
996
1012 Will skip hidden frames.
997 Will skip hidden frames.
1013 """
998 """
1014 if self.curindex + 1 == len(self.stack):
999 if self.curindex + 1 == len(self.stack):
1015 self.error("Newest frame")
1000 self.error("Newest frame")
1016 return
1001 return
1017 try:
1002 try:
1018 count = int(arg or 1)
1003 count = int(arg or 1)
1019 except ValueError:
1004 except ValueError:
1020 self.error("Invalid frame count (%s)" % arg)
1005 self.error("Invalid frame count (%s)" % arg)
1021 return
1006 return
1022 if count < 0:
1007 if count < 0:
1023 _newframe = len(self.stack) - 1
1008 _newframe = len(self.stack) - 1
1024 else:
1009 else:
1025 counter = 0
1010 counter = 0
1026 skipped = 0
1011 skipped = 0
1027 hidden_frames = self.hidden_frames(self.stack)
1012 hidden_frames = self.hidden_frames(self.stack)
1028 for i in range(self.curindex + 1, len(self.stack)):
1013 for i in range(self.curindex + 1, len(self.stack)):
1029 if hidden_frames[i] and self.skip_hidden:
1014 if hidden_frames[i] and self.skip_hidden:
1030 skipped += 1
1015 skipped += 1
1031 continue
1016 continue
1032 counter += 1
1017 counter += 1
1033 if counter >= count:
1018 if counter >= count:
1034 break
1019 break
1035 else:
1020 else:
1036 self.error(
1021 self.error(
1037 "all frames below hidden, use `skip_hidden False` to get get into those."
1022 "all frames below hidden, use `skip_hidden False` to get get into those."
1038 )
1023 )
1039 return
1024 return
1040
1025
1041 Colors = self.color_scheme_table.active_colors
1026 Colors = self.color_scheme_table.active_colors
1042 ColorsNormal = Colors.Normal
1027 ColorsNormal = Colors.Normal
1043 if skipped:
1028 if skipped:
1044 print(
1029 print(
1045 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1030 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1046 )
1031 )
1047 _newframe = i
1032 _newframe = i
1048
1033
1049 self._select_frame(_newframe)
1034 self._select_frame(_newframe)
1050
1035
1051 do_d = do_down
1036 do_d = do_down
1052 do_u = do_up
1037 do_u = do_up
1053
1038
1054 def do_context(self, context):
1039 def do_context(self, context):
1055 """context number_of_lines
1040 """context number_of_lines
1056 Set the number of lines of source code to show when displaying
1041 Set the number of lines of source code to show when displaying
1057 stacktrace information.
1042 stacktrace information.
1058 """
1043 """
1059 try:
1044 try:
1060 new_context = int(context)
1045 new_context = int(context)
1061 if new_context <= 0:
1046 if new_context <= 0:
1062 raise ValueError()
1047 raise ValueError()
1063 self.context = new_context
1048 self.context = new_context
1064 except ValueError:
1049 except ValueError:
1065 self.error("The 'context' command requires a positive integer argument.")
1050 self.error("The 'context' command requires a positive integer argument.")
1066
1051
1067
1052
1068 class InterruptiblePdb(Pdb):
1053 class InterruptiblePdb(Pdb):
1069 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1054 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1070
1055
1071 def cmdloop(self, intro=None):
1056 def cmdloop(self, intro=None):
1072 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1057 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1073 try:
1058 try:
1074 return OldPdb.cmdloop(self, intro=intro)
1059 return OldPdb.cmdloop(self, intro=intro)
1075 except KeyboardInterrupt:
1060 except KeyboardInterrupt:
1076 self.stop_here = lambda frame: False
1061 self.stop_here = lambda frame: False
1077 self.do_quit("")
1062 self.do_quit("")
1078 sys.settrace(None)
1063 sys.settrace(None)
1079 self.quitting = False
1064 self.quitting = False
1080 raise
1065 raise
1081
1066
1082 def _cmdloop(self):
1067 def _cmdloop(self):
1083 while True:
1068 while True:
1084 try:
1069 try:
1085 # keyboard interrupts allow for an easy way to cancel
1070 # keyboard interrupts allow for an easy way to cancel
1086 # the current command, so allow them during interactive input
1071 # the current command, so allow them during interactive input
1087 self.allow_kbdint = True
1072 self.allow_kbdint = True
1088 self.cmdloop()
1073 self.cmdloop()
1089 self.allow_kbdint = False
1074 self.allow_kbdint = False
1090 break
1075 break
1091 except KeyboardInterrupt:
1076 except KeyboardInterrupt:
1092 self.message('--KeyboardInterrupt--')
1077 self.message('--KeyboardInterrupt--')
1093 raise
1078 raise
1094
1079
1095
1080
1096 def set_trace(frame=None, header=None):
1081 def set_trace(frame=None, header=None):
1097 """
1082 """
1098 Start debugging from `frame`.
1083 Start debugging from `frame`.
1099
1084
1100 If frame is not specified, debugging starts from caller's frame.
1085 If frame is not specified, debugging starts from caller's frame.
1101 """
1086 """
1102 pdb = Pdb()
1087 pdb = Pdb()
1103 if header is not None:
1088 if header is not None:
1104 pdb.message(header)
1089 pdb.message(header)
1105 pdb.set_trace(frame or sys._getframe().f_back)
1090 pdb.set_trace(frame or sys._getframe().f_back)
@@ -1,175 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Color schemes for exception handling code in IPython.
3 Color schemes for exception handling code in IPython.
4 """
4 """
5
5
6 import os
6 import os
7
7
8 #*****************************************************************************
8 #*****************************************************************************
9 # Copyright (C) 2005-2006 Fernando Perez <fperez@colorado.edu>
9 # Copyright (C) 2005-2006 Fernando Perez <fperez@colorado.edu>
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #*****************************************************************************
13 #*****************************************************************************
14
14
15 from IPython.utils.coloransi import ColorSchemeTable, TermColors, ColorScheme
15 from IPython.utils.coloransi import ColorSchemeTable, TermColors, ColorScheme
16
16
17 def exception_colors():
17 def exception_colors():
18 """Return a color table with fields for exception reporting.
18 """Return a color table with fields for exception reporting.
19
19
20 The table is an instance of ColorSchemeTable with schemes added for
20 The table is an instance of ColorSchemeTable with schemes added for
21 'Neutral', 'Linux', 'LightBG' and 'NoColor' and fields for exception handling filled
21 'Neutral', 'Linux', 'LightBG' and 'NoColor' and fields for exception handling filled
22 in.
22 in.
23
23
24 Examples:
24 Examples:
25
25
26 >>> ec = exception_colors()
26 >>> ec = exception_colors()
27 >>> ec.active_scheme_name
27 >>> ec.active_scheme_name
28 ''
28 ''
29 >>> print(ec.active_colors)
29 >>> print(ec.active_colors)
30 None
30 None
31
31
32 Now we activate a color scheme:
32 Now we activate a color scheme:
33 >>> ec.set_active_scheme('NoColor')
33 >>> ec.set_active_scheme('NoColor')
34 >>> ec.active_scheme_name
34 >>> ec.active_scheme_name
35 'NoColor'
35 'NoColor'
36 >>> sorted(ec.active_colors.keys())
36 >>> sorted(ec.active_colors.keys())
37 ['Normal', 'caret', 'em', 'excName', 'filename', 'filenameEm', 'line',
37 ['Normal', 'breakpoint_disabled', 'breakpoint_enabled', 'caret', 'em',
38 'lineno', 'linenoEm', 'name', 'nameEm', 'normalEm', 'topline', 'vName',
38 'excName', 'filename', 'filenameEm', 'line', 'lineno', 'linenoEm', 'name',
39 'val', 'valEm']
39 'nameEm', 'normalEm', 'prompt', 'topline', 'vName', 'val', 'valEm']
40
40 """
41 """
41
42
42 ex_colors = ColorSchemeTable()
43 ex_colors = ColorSchemeTable()
43
44
44 # Populate it with color schemes
45 # Populate it with color schemes
45 C = TermColors # shorthand and local lookup
46 C = TermColors # shorthand and local lookup
46 ex_colors.add_scheme(
47 ex_colors.add_scheme(
47 ColorScheme(
48 ColorScheme(
48 "NoColor",
49 "NoColor",
49 {
50 {
50 # The color to be used for the top line
51 # The color to be used for the top line
51 "topline": C.NoColor,
52 "topline": C.NoColor,
52
53
53 # The colors to be used in the traceback
54 # The colors to be used in the traceback
54 "filename": C.NoColor,
55 "filename": C.NoColor,
55 "lineno": C.NoColor,
56 "lineno": C.NoColor,
56 "name": C.NoColor,
57 "name": C.NoColor,
57 "vName": C.NoColor,
58 "vName": C.NoColor,
58 "val": C.NoColor,
59 "val": C.NoColor,
59 "em": C.NoColor,
60 "em": C.NoColor,
60
61
61 # Emphasized colors for the last frame of the traceback
62 # Emphasized colors for the last frame of the traceback
62 "normalEm": C.NoColor,
63 "normalEm": C.NoColor,
63 "filenameEm": C.NoColor,
64 "filenameEm": C.NoColor,
64 "linenoEm": C.NoColor,
65 "linenoEm": C.NoColor,
65 "nameEm": C.NoColor,
66 "nameEm": C.NoColor,
66 "valEm": C.NoColor,
67 "valEm": C.NoColor,
67
68
68 # Colors for printing the exception
69 # Colors for printing the exception
69 "excName": C.NoColor,
70 "excName": C.NoColor,
70 "line": C.NoColor,
71 "line": C.NoColor,
71 "caret": C.NoColor,
72 "caret": C.NoColor,
72 "Normal": C.NoColor,
73 "Normal": C.NoColor,
74 # debugger
75 "prompt": C.NoColor,
76 "breakpoint_enabled": C.NoColor,
77 "breakpoint_disabled": C.NoColor,
73 },
78 },
74 )
79 )
75 )
80 )
76
81
77 # make some schemes as instances so we can copy them for modification easily
82 # make some schemes as instances so we can copy them for modification easily
78 ex_colors.add_scheme(
83 ex_colors.add_scheme(
79 ColorScheme(
84 ColorScheme(
80 "Linux",
85 "Linux",
81 {
86 {
82 # The color to be used for the top line
87 # The color to be used for the top line
83 "topline": C.LightRed,
88 "topline": C.LightRed,
84 # The colors to be used in the traceback
89 # The colors to be used in the traceback
85 "filename": C.Green,
90 "filename": C.Green,
86 "lineno": C.Green,
91 "lineno": C.Green,
87 "name": C.Purple,
92 "name": C.Purple,
88 "vName": C.Cyan,
93 "vName": C.Cyan,
89 "val": C.Green,
94 "val": C.Green,
90 "em": C.LightCyan,
95 "em": C.LightCyan,
91 # Emphasized colors for the last frame of the traceback
96 # Emphasized colors for the last frame of the traceback
92 "normalEm": C.LightCyan,
97 "normalEm": C.LightCyan,
93 "filenameEm": C.LightGreen,
98 "filenameEm": C.LightGreen,
94 "linenoEm": C.LightGreen,
99 "linenoEm": C.LightGreen,
95 "nameEm": C.LightPurple,
100 "nameEm": C.LightPurple,
96 "valEm": C.LightBlue,
101 "valEm": C.LightBlue,
97 # Colors for printing the exception
102 # Colors for printing the exception
98 "excName": C.LightRed,
103 "excName": C.LightRed,
99 "line": C.Yellow,
104 "line": C.Yellow,
100 "caret": C.White,
105 "caret": C.White,
101 "Normal": C.Normal,
106 "Normal": C.Normal,
107 # debugger
108 "prompt": C.Green,
109 "breakpoint_enabled": C.LightRed,
110 "breakpoint_disabled": C.Red,
102 },
111 },
103 )
112 )
104 )
113 )
105
114
106 # For light backgrounds, swap dark/light colors
115 # For light backgrounds, swap dark/light colors
107 ex_colors.add_scheme(
116 ex_colors.add_scheme(
108 ColorScheme(
117 ColorScheme(
109 "LightBG",
118 "LightBG",
110 {
119 {
111 # The color to be used for the top line
120 # The color to be used for the top line
112 "topline": C.Red,
121 "topline": C.Red,
113
122
114 # The colors to be used in the traceback
123 # The colors to be used in the traceback
115 "filename": C.LightGreen,
124 "filename": C.LightGreen,
116 "lineno": C.LightGreen,
125 "lineno": C.LightGreen,
117 "name": C.LightPurple,
126 "name": C.LightPurple,
118 "vName": C.Cyan,
127 "vName": C.Cyan,
119 "val": C.LightGreen,
128 "val": C.LightGreen,
120 "em": C.Cyan,
129 "em": C.Cyan,
121
130
122 # Emphasized colors for the last frame of the traceback
131 # Emphasized colors for the last frame of the traceback
123 "normalEm": C.Cyan,
132 "normalEm": C.Cyan,
124 "filenameEm": C.Green,
133 "filenameEm": C.Green,
125 "linenoEm": C.Green,
134 "linenoEm": C.Green,
126 "nameEm": C.Purple,
135 "nameEm": C.Purple,
127 "valEm": C.Blue,
136 "valEm": C.Blue,
128
137
129 # Colors for printing the exception
138 # Colors for printing the exception
130 "excName": C.Red,
139 "excName": C.Red,
131 # "line": C.Brown, # brown often is displayed as yellow
140 # "line": C.Brown, # brown often is displayed as yellow
132 "line": C.Red,
141 "line": C.Red,
133 "caret": C.Normal,
142 "caret": C.Normal,
134 "Normal": C.Normal,
143 "Normal": C.Normal,
144 # debugger
145 "prompt": C.Blue,
146 "breakpoint_enabled": C.LightRed,
147 "breakpoint_disabled": C.Red,
135 },
148 },
136 )
149 )
137 )
150 )
138
151
139 ex_colors.add_scheme(
152 ex_colors.add_scheme(
140 ColorScheme(
153 ColorScheme(
141 "Neutral",
154 "Neutral",
142 {
155 {
143 # The color to be used for the top line
156 # The color to be used for the top line
144 "topline": C.Red,
157 "topline": C.Red,
145 # The colors to be used in the traceback
158 # The colors to be used in the traceback
146 "filename": C.LightGreen,
159 "filename": C.LightGreen,
147 "lineno": C.LightGreen,
160 "lineno": C.LightGreen,
148 "name": C.LightPurple,
161 "name": C.LightPurple,
149 "vName": C.Cyan,
162 "vName": C.Cyan,
150 "val": C.LightGreen,
163 "val": C.LightGreen,
151 "em": C.Cyan,
164 "em": C.Cyan,
152 # Emphasized colors for the last frame of the traceback
165 # Emphasized colors for the last frame of the traceback
153 "normalEm": C.Cyan,
166 "normalEm": C.Cyan,
154 "filenameEm": C.Green,
167 "filenameEm": C.Green,
155 "linenoEm": C.Green,
168 "linenoEm": C.Green,
156 "nameEm": C.Purple,
169 "nameEm": C.Purple,
157 "valEm": C.Blue,
170 "valEm": C.Blue,
158 # Colors for printing the exception
171 # Colors for printing the exception
159 "excName": C.Red,
172 "excName": C.Red,
160 # line = C.Brown, # brown often is displayed as yellow
173 # line = C.Brown, # brown often is displayed as yellow
161 "line": C.Red,
174 "line": C.Red,
162 "caret": C.Normal,
175 "caret": C.Normal,
163 "Normal": C.Normal,
176 "Normal": C.Normal,
177 # debugger
178 "prompt": C.Blue,
179 "breakpoint_enabled": C.LightRed,
180 "breakpoint_disabled": C.Red,
164 },
181 },
165 )
182 )
166 )
183 )
167
184
168 # Hack: the 'neutral' colours are not very visible on a dark background on
185 # Hack: the 'neutral' colours are not very visible on a dark background on
169 # Windows. Since Windows command prompts have a dark background by default, and
186 # Windows. Since Windows command prompts have a dark background by default, and
170 # relatively few users are likely to alter that, we will use the 'Linux' colours,
187 # relatively few users are likely to alter that, we will use the 'Linux' colours,
171 # designed for a dark background, as the default on Windows.
188 # designed for a dark background, as the default on Windows.
172 if os.name == "nt":
189 if os.name == "nt":
173 ex_colors.add_scheme(ex_colors['Linux'].copy('Neutral'))
190 ex_colors.add_scheme(ex_colors['Linux'].copy('Neutral'))
174
191
175 return ex_colors
192 return ex_colors
@@ -1,221 +1,247 b''
1 """Tools for coloring text in ANSI terminals.
1 """Tools for coloring text in ANSI terminals.
2 """
2 """
3
3
4 #*****************************************************************************
4 #*****************************************************************************
5 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
5 # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #*****************************************************************************
9 #*****************************************************************************
10
10
11
11
12 import os
12 import os
13 import warnings
13 import warnings
14
14
15 from IPython.utils.ipstruct import Struct
15 from IPython.utils.ipstruct import Struct
16
16
17 __all__ = ["TermColors", "InputTermColors", "ColorScheme", "ColorSchemeTable"]
17 __all__ = ["TermColors", "InputTermColors", "ColorScheme", "ColorSchemeTable"]
18
18
19 _sentinel = object()
19 _sentinel = object()
20
20
21 color_templates = (
21 color_templates = (
22 # Dark colors
22 # Dark colors
23 ("Black" , "0;30"),
23 ("Black" , "0;30"),
24 ("Red" , "0;31"),
24 ("Red" , "0;31"),
25 ("Green" , "0;32"),
25 ("Green" , "0;32"),
26 ("Brown" , "0;33"),
26 ("Brown" , "0;33"),
27 ("Blue" , "0;34"),
27 ("Blue" , "0;34"),
28 ("Purple" , "0;35"),
28 ("Purple" , "0;35"),
29 ("Cyan" , "0;36"),
29 ("Cyan" , "0;36"),
30 ("LightGray" , "0;37"),
30 ("LightGray" , "0;37"),
31 # Light colors
31 # Light colors
32 ("DarkGray" , "1;30"),
32 ("DarkGray" , "1;30"),
33 ("LightRed" , "1;31"),
33 ("LightRed" , "1;31"),
34 ("LightGreen" , "1;32"),
34 ("LightGreen" , "1;32"),
35 ("Yellow" , "1;33"),
35 ("Yellow" , "1;33"),
36 ("LightBlue" , "1;34"),
36 ("LightBlue" , "1;34"),
37 ("LightPurple" , "1;35"),
37 ("LightPurple" , "1;35"),
38 ("LightCyan" , "1;36"),
38 ("LightCyan" , "1;36"),
39 ("White" , "1;37"),
39 ("White" , "1;37"),
40 # Blinking colors. Probably should not be used in anything serious.
40 # Blinking colors. Probably should not be used in anything serious.
41 ("BlinkBlack" , "5;30"),
41 ("BlinkBlack" , "5;30"),
42 ("BlinkRed" , "5;31"),
42 ("BlinkRed" , "5;31"),
43 ("BlinkGreen" , "5;32"),
43 ("BlinkGreen" , "5;32"),
44 ("BlinkYellow" , "5;33"),
44 ("BlinkYellow" , "5;33"),
45 ("BlinkBlue" , "5;34"),
45 ("BlinkBlue" , "5;34"),
46 ("BlinkPurple" , "5;35"),
46 ("BlinkPurple" , "5;35"),
47 ("BlinkCyan" , "5;36"),
47 ("BlinkCyan" , "5;36"),
48 ("BlinkLightGray", "5;37"),
48 ("BlinkLightGray", "5;37"),
49 )
49 )
50
50
51 def make_color_table(in_class):
51 def make_color_table(in_class):
52 """Build a set of color attributes in a class.
52 """Build a set of color attributes in a class.
53
53
54 Helper function for building the :class:`TermColors` and
54 Helper function for building the :class:`TermColors` and
55 :class`InputTermColors`.
55 :class`InputTermColors`.
56 """
56 """
57 for name,value in color_templates:
57 for name,value in color_templates:
58 setattr(in_class,name,in_class._base % value)
58 setattr(in_class,name,in_class._base % value)
59
59
60 class TermColors:
60 class TermColors:
61 """Color escape sequences.
61 """Color escape sequences.
62
62
63 This class defines the escape sequences for all the standard (ANSI?)
63 This class defines the escape sequences for all the standard (ANSI?)
64 colors in terminals. Also defines a NoColor escape which is just the null
64 colors in terminals. Also defines a NoColor escape which is just the null
65 string, suitable for defining 'dummy' color schemes in terminals which get
65 string, suitable for defining 'dummy' color schemes in terminals which get
66 confused by color escapes.
66 confused by color escapes.
67
67
68 This class should be used as a mixin for building color schemes."""
68 This class should be used as a mixin for building color schemes."""
69
69
70 NoColor = '' # for color schemes in color-less terminals.
70 NoColor = '' # for color schemes in color-less terminals.
71 Normal = '\033[0m' # Reset normal coloring
71 Normal = '\033[0m' # Reset normal coloring
72 _base = '\033[%sm' # Template for all other colors
72 _base = '\033[%sm' # Template for all other colors
73
73
74 # Build the actual color table as a set of class attributes:
74 Black = "0;30"
75 make_color_table(TermColors)
75 Red = "0;31"
76 Green = "0;32"
77 Brown = "0;33"
78 Blue = "0;34"
79 Purple = "0;35"
80 Cyan = "0;36"
81 LightGray = "0;37"
82 # Light colors
83 DarkGray = "1;31"
84 LightRed = "1;32"
85 LightGreen = "1;33"
86 Yellow = "1;34"
87 LightBlue = "1;35"
88 LightPurple = "1;36"
89 LightCyan = "1;37"
90 White = "1;38"
91 # Blinking colors. Probably should not be used in anything serious.
92 BlinkBlack = "5;30"
93 BlinkRed = "5;31"
94 BlinkGreen = "5;32"
95 BlinkYellow = "5;33"
96 BlinkBlue = "5;34"
97 BlinkPurple = "5;35"
98 BlinkCyan = "5;36"
99 BlinkLightGray = "5;37"
100
101
76
102
77 class InputTermColors:
103 class InputTermColors:
78 """Color escape sequences for input prompts.
104 """Color escape sequences for input prompts.
79
105
80 This class is similar to TermColors, but the escapes are wrapped in \\001
106 This class is similar to TermColors, but the escapes are wrapped in \\001
81 and \\002 so that readline can properly know the length of each line and
107 and \\002 so that readline can properly know the length of each line and
82 can wrap lines accordingly. Use this class for any colored text which
108 can wrap lines accordingly. Use this class for any colored text which
83 needs to be used in input prompts, such as in calls to raw_input().
109 needs to be used in input prompts, such as in calls to raw_input().
84
110
85 This class defines the escape sequences for all the standard (ANSI?)
111 This class defines the escape sequences for all the standard (ANSI?)
86 colors in terminals. Also defines a NoColor escape which is just the null
112 colors in terminals. Also defines a NoColor escape which is just the null
87 string, suitable for defining 'dummy' color schemes in terminals which get
113 string, suitable for defining 'dummy' color schemes in terminals which get
88 confused by color escapes.
114 confused by color escapes.
89
115
90 This class should be used as a mixin for building color schemes."""
116 This class should be used as a mixin for building color schemes."""
91
117
92 NoColor = '' # for color schemes in color-less terminals.
118 NoColor = '' # for color schemes in color-less terminals.
93
119
94 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
120 if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
95 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
121 # (X)emacs on W32 gets confused with \001 and \002 so we remove them
96 Normal = '\033[0m' # Reset normal coloring
122 Normal = '\033[0m' # Reset normal coloring
97 _base = '\033[%sm' # Template for all other colors
123 _base = '\033[%sm' # Template for all other colors
98 else:
124 else:
99 Normal = '\001\033[0m\002' # Reset normal coloring
125 Normal = '\001\033[0m\002' # Reset normal coloring
100 _base = '\001\033[%sm\002' # Template for all other colors
126 _base = '\001\033[%sm\002' # Template for all other colors
101
127
102 # Build the actual color table as a set of class attributes:
128 # Build the actual color table as a set of class attributes:
103 make_color_table(InputTermColors)
129 make_color_table(InputTermColors)
104
130
105 class NoColors:
131 class NoColors:
106 """This defines all the same names as the colour classes, but maps them to
132 """This defines all the same names as the colour classes, but maps them to
107 empty strings, so it can easily be substituted to turn off colours."""
133 empty strings, so it can easily be substituted to turn off colours."""
108 NoColor = ''
134 NoColor = ''
109 Normal = ''
135 Normal = ''
110
136
111 for name, value in color_templates:
137 for name, value in color_templates:
112 setattr(NoColors, name, '')
138 setattr(NoColors, name, '')
113
139
114 class ColorScheme:
140 class ColorScheme:
115 """Generic color scheme class. Just a name and a Struct."""
141 """Generic color scheme class. Just a name and a Struct."""
116
142
117 name: str
143 name: str
118 colors: Struct
144 colors: Struct
119
145
120 def __init__(self, __scheme_name_, colordict=None, **colormap):
146 def __init__(self, __scheme_name_, colordict=None, **colormap):
121 self.name = __scheme_name_
147 self.name = __scheme_name_
122 if colormap:
148 if colormap:
123 warnings.warn(
149 warnings.warn(
124 "Passing each colors as a kwarg to ColorScheme is "
150 "Passing each colors as a kwarg to ColorScheme is "
125 "considered for deprecation. Please pass a "
151 "considered for deprecation. Please pass a "
126 "a single dict as second parameter. If you are using this"
152 "a single dict as second parameter. If you are using this"
127 "feature, please comment an subscribe to issue "
153 "feature, please comment an subscribe to issue "
128 "https://github.com/ipython/ipython/issues/14304",
154 "https://github.com/ipython/ipython/issues/14304",
129 PendingDeprecationWarning,
155 PendingDeprecationWarning,
130 stacklevel=2,
156 stacklevel=2,
131 )
157 )
132 if colordict is None:
158 if colordict is None:
133 self.colors = Struct(**colormap)
159 self.colors = Struct(**colormap)
134 else:
160 else:
135 self.colors = Struct(colordict)
161 self.colors = Struct(colordict)
136
162
137 def copy(self,name=None):
163 def copy(self,name=None):
138 """Return a full copy of the object, optionally renaming it."""
164 """Return a full copy of the object, optionally renaming it."""
139 if name is None:
165 if name is None:
140 name = self.name
166 name = self.name
141 return ColorScheme(name, self.colors.dict())
167 return ColorScheme(name, self.colors.dict())
142
168
143 class ColorSchemeTable(dict):
169 class ColorSchemeTable(dict):
144 """General class to handle tables of color schemes.
170 """General class to handle tables of color schemes.
145
171
146 It's basically a dict of color schemes with a couple of shorthand
172 It's basically a dict of color schemes with a couple of shorthand
147 attributes and some convenient methods.
173 attributes and some convenient methods.
148
174
149 active_scheme_name -> obvious
175 active_scheme_name -> obvious
150 active_colors -> actual color table of the active scheme"""
176 active_colors -> actual color table of the active scheme"""
151
177
152 def __init__(self, scheme_list=None, default_scheme=''):
178 def __init__(self, scheme_list=None, default_scheme=''):
153 """Create a table of color schemes.
179 """Create a table of color schemes.
154
180
155 The table can be created empty and manually filled or it can be
181 The table can be created empty and manually filled or it can be
156 created with a list of valid color schemes AND the specification for
182 created with a list of valid color schemes AND the specification for
157 the default active scheme.
183 the default active scheme.
158 """
184 """
159
185
160 # create object attributes to be set later
186 # create object attributes to be set later
161 self.active_scheme_name = ''
187 self.active_scheme_name = ''
162 self.active_colors = None
188 self.active_colors = None
163
189
164 if scheme_list:
190 if scheme_list:
165 if default_scheme == '':
191 if default_scheme == '':
166 raise ValueError('you must specify the default color scheme')
192 raise ValueError('you must specify the default color scheme')
167 for scheme in scheme_list:
193 for scheme in scheme_list:
168 self.add_scheme(scheme)
194 self.add_scheme(scheme)
169 self.set_active_scheme(default_scheme)
195 self.set_active_scheme(default_scheme)
170
196
171 def copy(self):
197 def copy(self):
172 """Return full copy of object"""
198 """Return full copy of object"""
173 return ColorSchemeTable(self.values(),self.active_scheme_name)
199 return ColorSchemeTable(self.values(),self.active_scheme_name)
174
200
175 def __setitem__(self, key: str, value: ColorScheme):
201 def __setitem__(self, key: str, value: ColorScheme):
176 assert isinstance(key, str)
202 assert isinstance(key, str)
177 assert isinstance(value, ColorScheme)
203 assert isinstance(value, ColorScheme)
178 super().__setitem__(key, value)
204 super().__setitem__(key, value)
179
205
180 def add_scheme(self, new_scheme):
206 def add_scheme(self, new_scheme):
181 """Add a new color scheme to the table."""
207 """Add a new color scheme to the table."""
182 if not isinstance(new_scheme, ColorScheme):
208 if not isinstance(new_scheme, ColorScheme):
183 raise ValueError('ColorSchemeTable only accepts ColorScheme instances')
209 raise ValueError('ColorSchemeTable only accepts ColorScheme instances')
184 self[new_scheme.name] = new_scheme
210 self[new_scheme.name] = new_scheme
185
211
186 def set_active_scheme(self, scheme, case_sensitive=_sentinel):
212 def set_active_scheme(self, scheme, case_sensitive=_sentinel):
187 """Set the currently active scheme.
213 """Set the currently active scheme.
188
214
189 Names are by default compared in a case-insensitive way, but this can
215 Names are by default compared in a case-insensitive way, but this can
190 be changed by setting the parameter case_sensitive to true."""
216 be changed by setting the parameter case_sensitive to true."""
191
217
192 if case_sensitive is _sentinel:
218 if case_sensitive is _sentinel:
193 case_sensitive = False
219 case_sensitive = False
194 else:
220 else:
195 warnings.warn(
221 warnings.warn(
196 "set_active_scheme(case_sensitive=...) is Pending "
222 "set_active_scheme(case_sensitive=...) is Pending "
197 "deprecation. Please comment on "
223 "deprecation. Please comment on "
198 "https://github.com/ipython/ipython/issues/14306 "
224 "https://github.com/ipython/ipython/issues/14306 "
199 "to let the ipython maintainer that you are affected.",
225 "to let the ipython maintainer that you are affected.",
200 PendingDeprecationWarning,
226 PendingDeprecationWarning,
201 stacklevel=2,
227 stacklevel=2,
202 )
228 )
203
229
204 scheme_names = list(self.keys())
230 scheme_names = list(self.keys())
205 if case_sensitive:
231 if case_sensitive:
206 valid_schemes = scheme_names
232 valid_schemes = scheme_names
207 scheme_test = scheme
233 scheme_test = scheme
208 else:
234 else:
209 valid_schemes = [s.lower() for s in scheme_names]
235 valid_schemes = [s.lower() for s in scheme_names]
210 scheme_test = scheme.lower()
236 scheme_test = scheme.lower()
211 try:
237 try:
212 scheme_idx = valid_schemes.index(scheme_test)
238 scheme_idx = valid_schemes.index(scheme_test)
213 except ValueError as e:
239 except ValueError as e:
214 raise ValueError('Unrecognized color scheme: ' + scheme + \
240 raise ValueError('Unrecognized color scheme: ' + scheme + \
215 '\nValid schemes: '+str(scheme_names).replace("'', ",'')) from e
241 '\nValid schemes: '+str(scheme_names).replace("'', ",'')) from e
216 else:
242 else:
217 active = scheme_names[scheme_idx]
243 active = scheme_names[scheme_idx]
218 self.active_scheme_name = active
244 self.active_scheme_name = active
219 self.active_colors = self[active].colors
245 self.active_colors = self[active].colors
220 # Now allow using '' as an index for the current active scheme
246 # Now allow using '' as an index for the current active scheme
221 self[''] = self[active]
247 self[''] = self[active]
General Comments 0
You need to be logged in to leave comments. Login now