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