##// 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 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 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
153 153
154 154
155 155 def strip_indentation(multiline_string):
156 156 return RGX_EXTRA_INDENT.sub('', multiline_string)
157 157
158 158
159 159 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
160 160 """Make new_fn have old_fn's doc string. This is particularly useful
161 161 for the ``do_...`` commands that hook into the help system.
162 162 Adapted from from a comp.lang.python posting
163 163 by Duncan Booth."""
164 164 def wrapper(*args, **kw):
165 165 return new_fn(*args, **kw)
166 166 if old_fn.__doc__:
167 167 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
168 168 return wrapper
169 169
170 170
171 171 class Pdb(OldPdb):
172 172 """Modified Pdb class, does not load readline.
173 173
174 174 for a standalone version that uses prompt_toolkit, see
175 175 `IPython.terminal.debugger.TerminalPdb` and
176 176 `IPython.terminal.debugger.set_trace()`
177 177
178 178
179 179 This debugger can hide and skip frames that are tagged according to some predicates.
180 180 See the `skip_predicates` commands.
181 181
182 182 """
183 183
184 184 shell: InteractiveShell
185 185
186 186 if CHAIN_EXCEPTIONS:
187 187 MAX_CHAINED_EXCEPTION_DEPTH = 999
188 188
189 189 default_predicates = {
190 190 "tbhide": True,
191 191 "readonly": False,
192 192 "ipython_internal": True,
193 193 "debuggerskip": True,
194 194 }
195 195
196 196 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
197 197 """Create a new IPython debugger.
198 198
199 199 Parameters
200 200 ----------
201 201 completekey : default None
202 202 Passed to pdb.Pdb.
203 203 stdin : default None
204 204 Passed to pdb.Pdb.
205 205 stdout : default None
206 206 Passed to pdb.Pdb.
207 207 context : int
208 208 Number of lines of source code context to show when
209 209 displaying stacktrace information.
210 210 **kwargs
211 211 Passed to pdb.Pdb.
212 212
213 213 Notes
214 214 -----
215 215 The possibilities are python version dependent, see the python
216 216 docs for more info.
217 217 """
218 218
219 219 # Parent constructor:
220 220 try:
221 221 self.context = int(context)
222 222 if self.context <= 0:
223 223 raise ValueError("Context must be a positive integer")
224 224 except (TypeError, ValueError) as e:
225 225 raise ValueError("Context must be a positive integer") from e
226 226
227 227 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
228 228 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
229 229
230 230 # IPython changes...
231 231 self.shell = get_ipython()
232 232
233 233 if self.shell is None:
234 234 save_main = sys.modules['__main__']
235 235 # No IPython instance running, we must create one
236 236 from IPython.terminal.interactiveshell import \
237 237 TerminalInteractiveShell
238 238 self.shell = TerminalInteractiveShell.instance()
239 239 # needed by any code which calls __import__("__main__") after
240 240 # the debugger was entered. See also #9941.
241 241 sys.modules["__main__"] = save_main
242 242
243 243
244 244 color_scheme = self.shell.colors
245 245
246 246 self.aliases = {}
247 247
248 248 # Create color table: we copy the default one from the traceback
249 249 # module and add a few attributes needed for debugging
250 250 self.color_scheme_table = exception_colors()
251 251
252 252 # shorthands
253 253 C = coloransi.TermColors
254 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 257 # Add a python parser so we can syntax highlight source while
273 258 # debugging.
274 259 self.parser = PyColorize.Parser(style=color_scheme)
275 260 self.set_colors(color_scheme)
276 261
277 262 # Set the prompt - the default prompt is '(Pdb)'
278 263 self.prompt = prompt
279 264 self.skip_hidden = True
280 265 self.report_skipped = True
281 266
282 267 # list of predicates we use to skip frames
283 268 self._predicates = self.default_predicates
284 269
285 270 if CHAIN_EXCEPTIONS:
286 271 self._chained_exceptions = tuple()
287 272 self._chained_exception_index = 0
288 273
289 274 #
290 275 def set_colors(self, scheme):
291 276 """Shorthand access to the color table scheme selector method."""
292 277 self.color_scheme_table.set_active_scheme(scheme)
293 278 self.parser.style = scheme
294 279
295 280 def set_trace(self, frame=None):
296 281 if frame is None:
297 282 frame = sys._getframe().f_back
298 283 self.initial_frame = frame
299 284 return super().set_trace(frame)
300 285
301 286 def _hidden_predicate(self, frame):
302 287 """
303 288 Given a frame return whether it it should be hidden or not by IPython.
304 289 """
305 290
306 291 if self._predicates["readonly"]:
307 292 fname = frame.f_code.co_filename
308 293 # we need to check for file existence and interactively define
309 294 # function would otherwise appear as RO.
310 295 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
311 296 return True
312 297
313 298 if self._predicates["tbhide"]:
314 299 if frame in (self.curframe, getattr(self, "initial_frame", None)):
315 300 return False
316 301 frame_locals = self._get_frame_locals(frame)
317 302 if "__tracebackhide__" not in frame_locals:
318 303 return False
319 304 return frame_locals["__tracebackhide__"]
320 305 return False
321 306
322 307 def hidden_frames(self, stack):
323 308 """
324 309 Given an index in the stack return whether it should be skipped.
325 310
326 311 This is used in up/down and where to skip frames.
327 312 """
328 313 # The f_locals dictionary is updated from the actual frame
329 314 # locals whenever the .f_locals accessor is called, so we
330 315 # avoid calling it here to preserve self.curframe_locals.
331 316 # Furthermore, there is no good reason to hide the current frame.
332 317 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
333 318 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
334 319 if ip_start and self._predicates["ipython_internal"]:
335 320 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
336 321 return ip_hide
337 322
338 323 if CHAIN_EXCEPTIONS:
339 324
340 325 def _get_tb_and_exceptions(self, tb_or_exc):
341 326 """
342 327 Given a tracecack or an exception, return a tuple of chained exceptions
343 328 and current traceback to inspect.
344 329 This will deal with selecting the right ``__cause__`` or ``__context__``
345 330 as well as handling cycles, and return a flattened list of exceptions we
346 331 can jump to with do_exceptions.
347 332 """
348 333 _exceptions = []
349 334 if isinstance(tb_or_exc, BaseException):
350 335 traceback, current = tb_or_exc.__traceback__, tb_or_exc
351 336
352 337 while current is not None:
353 338 if current in _exceptions:
354 339 break
355 340 _exceptions.append(current)
356 341 if current.__cause__ is not None:
357 342 current = current.__cause__
358 343 elif (
359 344 current.__context__ is not None
360 345 and not current.__suppress_context__
361 346 ):
362 347 current = current.__context__
363 348
364 349 if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
365 350 self.message(
366 351 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
367 352 " chained exceptions found, not all exceptions"
368 353 "will be browsable with `exceptions`."
369 354 )
370 355 break
371 356 else:
372 357 traceback = tb_or_exc
373 358 return tuple(reversed(_exceptions)), traceback
374 359
375 360 @contextmanager
376 361 def _hold_exceptions(self, exceptions):
377 362 """
378 363 Context manager to ensure proper cleaning of exceptions references
379 364 When given a chained exception instead of a traceback,
380 365 pdb may hold references to many objects which may leak memory.
381 366 We use this context manager to make sure everything is properly cleaned
382 367 """
383 368 try:
384 369 self._chained_exceptions = exceptions
385 370 self._chained_exception_index = len(exceptions) - 1
386 371 yield
387 372 finally:
388 373 # we can't put those in forget as otherwise they would
389 374 # be cleared on exception change
390 375 self._chained_exceptions = tuple()
391 376 self._chained_exception_index = 0
392 377
393 378 def do_exceptions(self, arg):
394 379 """exceptions [number]
395 380 List or change current exception in an exception chain.
396 381 Without arguments, list all the current exception in the exception
397 382 chain. Exceptions will be numbered, with the current exception indicated
398 383 with an arrow.
399 384 If given an integer as argument, switch to the exception at that index.
400 385 """
401 386 if not self._chained_exceptions:
402 387 self.message(
403 388 "Did not find chained exceptions. To move between"
404 389 " exceptions, pdb/post_mortem must be given an exception"
405 390 " object rather than a traceback."
406 391 )
407 392 return
408 393 if not arg:
409 394 for ix, exc in enumerate(self._chained_exceptions):
410 395 prompt = ">" if ix == self._chained_exception_index else " "
411 396 rep = repr(exc)
412 397 if len(rep) > 80:
413 398 rep = rep[:77] + "..."
414 399 indicator = (
415 400 " -"
416 401 if self._chained_exceptions[ix].__traceback__ is None
417 402 else f"{ix:>3}"
418 403 )
419 404 self.message(f"{prompt} {indicator} {rep}")
420 405 else:
421 406 try:
422 407 number = int(arg)
423 408 except ValueError:
424 409 self.error("Argument must be an integer")
425 410 return
426 411 if 0 <= number < len(self._chained_exceptions):
427 412 if self._chained_exceptions[number].__traceback__ is None:
428 413 self.error(
429 414 "This exception does not have a traceback, cannot jump to it"
430 415 )
431 416 return
432 417
433 418 self._chained_exception_index = number
434 419 self.setup(None, self._chained_exceptions[number].__traceback__)
435 420 self.print_stack_entry(self.stack[self.curindex])
436 421 else:
437 422 self.error("No exception with that number")
438 423
439 424 def interaction(self, frame, tb_or_exc):
440 425 try:
441 426 if CHAIN_EXCEPTIONS:
442 427 # this context manager is part of interaction in 3.13
443 428 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
444 429 if isinstance(tb_or_exc, BaseException):
445 430 assert tb is not None, "main exception must have a traceback"
446 431 with self._hold_exceptions(_chained_exceptions):
447 432 OldPdb.interaction(self, frame, tb)
448 433 else:
449 434 OldPdb.interaction(self, frame, tb_or_exc)
450 435
451 436 except KeyboardInterrupt:
452 437 self.stdout.write("\n" + self.shell.get_exception_only())
453 438
454 439 def precmd(self, line):
455 440 """Perform useful escapes on the command before it is executed."""
456 441
457 442 if line.endswith("??"):
458 443 line = "pinfo2 " + line[:-2]
459 444 elif line.endswith("?"):
460 445 line = "pinfo " + line[:-1]
461 446
462 447 line = super().precmd(line)
463 448
464 449 return line
465 450
466 451 def new_do_quit(self, arg):
467 452 return OldPdb.do_quit(self, arg)
468 453
469 454 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
470 455
471 456 def print_stack_trace(self, context=None):
472 457 Colors = self.color_scheme_table.active_colors
473 458 ColorsNormal = Colors.Normal
474 459 if context is None:
475 460 context = self.context
476 461 try:
477 462 context = int(context)
478 463 if context <= 0:
479 464 raise ValueError("Context must be a positive integer")
480 465 except (TypeError, ValueError) as e:
481 466 raise ValueError("Context must be a positive integer") from e
482 467 try:
483 468 skipped = 0
484 469 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
485 470 if hidden and self.skip_hidden:
486 471 skipped += 1
487 472 continue
488 473 if skipped:
489 474 print(
490 475 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
491 476 )
492 477 skipped = 0
493 478 self.print_stack_entry(frame_lineno, context=context)
494 479 if skipped:
495 480 print(
496 481 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
497 482 )
498 483 except KeyboardInterrupt:
499 484 pass
500 485
501 486 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
502 487 context=None):
503 488 if context is None:
504 489 context = self.context
505 490 try:
506 491 context = int(context)
507 492 if context <= 0:
508 493 raise ValueError("Context must be a positive integer")
509 494 except (TypeError, ValueError) as e:
510 495 raise ValueError("Context must be a positive integer") from e
511 496 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
512 497
513 498 # vds: >>
514 499 frame, lineno = frame_lineno
515 500 filename = frame.f_code.co_filename
516 501 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
517 502 # vds: <<
518 503
519 504 def _get_frame_locals(self, frame):
520 505 """ "
521 506 Accessing f_local of current frame reset the namespace, so we want to avoid
522 507 that or the following can happen
523 508
524 509 ipdb> foo
525 510 "old"
526 511 ipdb> foo = "new"
527 512 ipdb> foo
528 513 "new"
529 514 ipdb> where
530 515 ipdb> foo
531 516 "old"
532 517
533 518 So if frame is self.current_frame we instead return self.curframe_locals
534 519
535 520 """
536 521 if frame is self.curframe:
537 522 return self.curframe_locals
538 523 else:
539 524 return frame.f_locals
540 525
541 526 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
542 527 if context is None:
543 528 context = self.context
544 529 try:
545 530 context = int(context)
546 531 if context <= 0:
547 532 print("Context must be a positive integer", file=self.stdout)
548 533 except (TypeError, ValueError):
549 534 print("Context must be a positive integer", file=self.stdout)
550 535
551 536 import reprlib
552 537
553 538 ret = []
554 539
555 540 Colors = self.color_scheme_table.active_colors
556 541 ColorsNormal = Colors.Normal
557 542 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
558 543 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
559 544 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
560 545 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
561 546
562 547 frame, lineno = frame_lineno
563 548
564 549 return_value = ''
565 550 loc_frame = self._get_frame_locals(frame)
566 551 if "__return__" in loc_frame:
567 552 rv = loc_frame["__return__"]
568 553 # return_value += '->'
569 554 return_value += reprlib.repr(rv) + "\n"
570 555 ret.append(return_value)
571 556
572 557 #s = filename + '(' + `lineno` + ')'
573 558 filename = self.canonic(frame.f_code.co_filename)
574 559 link = tpl_link % py3compat.cast_unicode(filename)
575 560
576 561 if frame.f_code.co_name:
577 562 func = frame.f_code.co_name
578 563 else:
579 564 func = "<lambda>"
580 565
581 566 call = ""
582 567 if func != "?":
583 568 if "__args__" in loc_frame:
584 569 args = reprlib.repr(loc_frame["__args__"])
585 570 else:
586 571 args = '()'
587 572 call = tpl_call % (func, args)
588 573
589 574 # The level info should be generated in the same format pdb uses, to
590 575 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
591 576 if frame is self.curframe:
592 577 ret.append('> ')
593 578 else:
594 579 ret.append(" ")
595 580 ret.append("%s(%s)%s\n" % (link, lineno, call))
596 581
597 582 start = lineno - 1 - context//2
598 583 lines = linecache.getlines(filename)
599 584 start = min(start, len(lines) - context)
600 585 start = max(start, 0)
601 586 lines = lines[start : start + context]
602 587
603 588 for i, line in enumerate(lines):
604 589 show_arrow = start + 1 + i == lineno
605 590 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
606 591 ret.append(
607 592 self.__format_line(
608 593 linetpl, filename, start + 1 + i, line, arrow=show_arrow
609 594 )
610 595 )
611 596 return "".join(ret)
612 597
613 598 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
614 599 bp_mark = ""
615 600 bp_mark_color = ""
616 601
617 602 new_line, err = self.parser.format2(line, 'str')
618 603 if not err:
619 604 line = new_line
620 605
621 606 bp = None
622 607 if lineno in self.get_file_breaks(filename):
623 608 bps = self.get_breaks(filename, lineno)
624 609 bp = bps[-1]
625 610
626 611 if bp:
627 612 Colors = self.color_scheme_table.active_colors
628 613 bp_mark = str(bp.number)
629 614 bp_mark_color = Colors.breakpoint_enabled
630 615 if not bp.enabled:
631 616 bp_mark_color = Colors.breakpoint_disabled
632 617
633 618 numbers_width = 7
634 619 if arrow:
635 620 # This is the line with the error
636 621 pad = numbers_width - len(str(lineno)) - len(bp_mark)
637 622 num = '%s%s' % (make_arrow(pad), str(lineno))
638 623 else:
639 624 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
640 625
641 626 return tpl_line % (bp_mark_color + bp_mark, num, line)
642 627
643 628 def print_list_lines(self, filename, first, last):
644 629 """The printing (as opposed to the parsing part of a 'list'
645 630 command."""
646 631 try:
647 632 Colors = self.color_scheme_table.active_colors
648 633 ColorsNormal = Colors.Normal
649 634 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
650 635 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
651 636 src = []
652 637 if filename == "<string>" and hasattr(self, "_exec_filename"):
653 638 filename = self._exec_filename
654 639
655 640 for lineno in range(first, last+1):
656 641 line = linecache.getline(filename, lineno)
657 642 if not line:
658 643 break
659 644
660 645 if lineno == self.curframe.f_lineno:
661 646 line = self.__format_line(
662 647 tpl_line_em, filename, lineno, line, arrow=True
663 648 )
664 649 else:
665 650 line = self.__format_line(
666 651 tpl_line, filename, lineno, line, arrow=False
667 652 )
668 653
669 654 src.append(line)
670 655 self.lineno = lineno
671 656
672 657 print(''.join(src), file=self.stdout)
673 658
674 659 except KeyboardInterrupt:
675 660 pass
676 661
677 662 def do_skip_predicates(self, args):
678 663 """
679 664 Turn on/off individual predicates as to whether a frame should be hidden/skip.
680 665
681 666 The global option to skip (or not) hidden frames is set with skip_hidden
682 667
683 668 To change the value of a predicate
684 669
685 670 skip_predicates key [true|false]
686 671
687 672 Call without arguments to see the current values.
688 673
689 674 To permanently change the value of an option add the corresponding
690 675 command to your ``~/.pdbrc`` file. If you are programmatically using the
691 676 Pdb instance you can also change the ``default_predicates`` class
692 677 attribute.
693 678 """
694 679 if not args.strip():
695 680 print("current predicates:")
696 681 for p, v in self._predicates.items():
697 682 print(" ", p, ":", v)
698 683 return
699 684 type_value = args.strip().split(" ")
700 685 if len(type_value) != 2:
701 686 print(
702 687 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
703 688 )
704 689 return
705 690
706 691 type_, value = type_value
707 692 if type_ not in self._predicates:
708 693 print(f"{type_!r} not in {set(self._predicates.keys())}")
709 694 return
710 695 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
711 696 print(
712 697 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
713 698 )
714 699 return
715 700
716 701 self._predicates[type_] = value.lower() in ("true", "yes", "1")
717 702 if not any(self._predicates.values()):
718 703 print(
719 704 "Warning, all predicates set to False, skip_hidden may not have any effects."
720 705 )
721 706
722 707 def do_skip_hidden(self, arg):
723 708 """
724 709 Change whether or not we should skip frames with the
725 710 __tracebackhide__ attribute.
726 711 """
727 712 if not arg.strip():
728 713 print(
729 714 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
730 715 )
731 716 elif arg.strip().lower() in ("true", "yes"):
732 717 self.skip_hidden = True
733 718 elif arg.strip().lower() in ("false", "no"):
734 719 self.skip_hidden = False
735 720 if not any(self._predicates.values()):
736 721 print(
737 722 "Warning, all predicates set to False, skip_hidden may not have any effects."
738 723 )
739 724
740 725 def do_list(self, arg):
741 726 """Print lines of code from the current stack frame
742 727 """
743 728 self.lastcmd = 'list'
744 729 last = None
745 730 if arg and arg != ".":
746 731 try:
747 732 x = eval(arg, {}, {})
748 733 if type(x) == type(()):
749 734 first, last = x
750 735 first = int(first)
751 736 last = int(last)
752 737 if last < first:
753 738 # Assume it's a count
754 739 last = first + last
755 740 else:
756 741 first = max(1, int(x) - 5)
757 742 except:
758 743 print('*** Error in argument:', repr(arg), file=self.stdout)
759 744 return
760 745 elif self.lineno is None or arg == ".":
761 746 first = max(1, self.curframe.f_lineno - 5)
762 747 else:
763 748 first = self.lineno + 1
764 749 if last is None:
765 750 last = first + 10
766 751 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
767 752
768 753 # vds: >>
769 754 lineno = first
770 755 filename = self.curframe.f_code.co_filename
771 756 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
772 757 # vds: <<
773 758
774 759 do_l = do_list
775 760
776 761 def getsourcelines(self, obj):
777 762 lines, lineno = inspect.findsource(obj)
778 763 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
779 764 # must be a module frame: do not try to cut a block out of it
780 765 return lines, 1
781 766 elif inspect.ismodule(obj):
782 767 return lines, 1
783 768 return inspect.getblock(lines[lineno:]), lineno+1
784 769
785 770 def do_longlist(self, arg):
786 771 """Print lines of code from the current stack frame.
787 772
788 773 Shows more lines than 'list' does.
789 774 """
790 775 self.lastcmd = 'longlist'
791 776 try:
792 777 lines, lineno = self.getsourcelines(self.curframe)
793 778 except OSError as err:
794 779 self.error(err)
795 780 return
796 781 last = lineno + len(lines)
797 782 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
798 783 do_ll = do_longlist
799 784
800 785 def do_debug(self, arg):
801 786 """debug code
802 787 Enter a recursive debugger that steps through the code
803 788 argument (which is an arbitrary expression or statement to be
804 789 executed in the current environment).
805 790 """
806 791 trace_function = sys.gettrace()
807 792 sys.settrace(None)
808 793 globals = self.curframe.f_globals
809 794 locals = self.curframe_locals
810 795 p = self.__class__(completekey=self.completekey,
811 796 stdin=self.stdin, stdout=self.stdout)
812 797 p.use_rawinput = self.use_rawinput
813 798 p.prompt = "(%s) " % self.prompt.strip()
814 799 self.message("ENTERING RECURSIVE DEBUGGER")
815 800 sys.call_tracing(p.run, (arg, globals, locals))
816 801 self.message("LEAVING RECURSIVE DEBUGGER")
817 802 sys.settrace(trace_function)
818 803 self.lastcmd = p.lastcmd
819 804
820 805 def do_pdef(self, arg):
821 806 """Print the call signature for any callable object.
822 807
823 808 The debugger interface to %pdef"""
824 809 namespaces = [
825 810 ("Locals", self.curframe_locals),
826 811 ("Globals", self.curframe.f_globals),
827 812 ]
828 813 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
829 814
830 815 def do_pdoc(self, arg):
831 816 """Print the docstring for an object.
832 817
833 818 The debugger interface to %pdoc."""
834 819 namespaces = [
835 820 ("Locals", self.curframe_locals),
836 821 ("Globals", self.curframe.f_globals),
837 822 ]
838 823 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
839 824
840 825 def do_pfile(self, arg):
841 826 """Print (or run through pager) the file where an object is defined.
842 827
843 828 The debugger interface to %pfile.
844 829 """
845 830 namespaces = [
846 831 ("Locals", self.curframe_locals),
847 832 ("Globals", self.curframe.f_globals),
848 833 ]
849 834 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
850 835
851 836 def do_pinfo(self, arg):
852 837 """Provide detailed information about an object.
853 838
854 839 The debugger interface to %pinfo, i.e., obj?."""
855 840 namespaces = [
856 841 ("Locals", self.curframe_locals),
857 842 ("Globals", self.curframe.f_globals),
858 843 ]
859 844 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
860 845
861 846 def do_pinfo2(self, arg):
862 847 """Provide extra detailed information about an object.
863 848
864 849 The debugger interface to %pinfo2, i.e., obj??."""
865 850 namespaces = [
866 851 ("Locals", self.curframe_locals),
867 852 ("Globals", self.curframe.f_globals),
868 853 ]
869 854 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
870 855
871 856 def do_psource(self, arg):
872 857 """Print (or run through pager) the source code for an object."""
873 858 namespaces = [
874 859 ("Locals", self.curframe_locals),
875 860 ("Globals", self.curframe.f_globals),
876 861 ]
877 862 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
878 863
879 864 def do_where(self, arg):
880 865 """w(here)
881 866 Print a stack trace, with the most recent frame at the bottom.
882 867 An arrow indicates the "current frame", which determines the
883 868 context of most commands. 'bt' is an alias for this command.
884 869
885 870 Take a number as argument as an (optional) number of context line to
886 871 print"""
887 872 if arg:
888 873 try:
889 874 context = int(arg)
890 875 except ValueError as err:
891 876 self.error(err)
892 877 return
893 878 self.print_stack_trace(context)
894 879 else:
895 880 self.print_stack_trace()
896 881
897 882 do_w = do_where
898 883
899 884 def break_anywhere(self, frame):
900 885 """
901 886 _stop_in_decorator_internals is overly restrictive, as we may still want
902 887 to trace function calls, so we need to also update break_anywhere so
903 888 that is we don't `stop_here`, because of debugger skip, we may still
904 889 stop at any point inside the function
905 890
906 891 """
907 892
908 893 sup = super().break_anywhere(frame)
909 894 if sup:
910 895 return sup
911 896 if self._predicates["debuggerskip"]:
912 897 if DEBUGGERSKIP in frame.f_code.co_varnames:
913 898 return True
914 899 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
915 900 return True
916 901 return False
917 902
918 903 def _is_in_decorator_internal_and_should_skip(self, frame):
919 904 """
920 905 Utility to tell us whether we are in a decorator internal and should stop.
921 906
922 907 """
923 908 # if we are disabled don't skip
924 909 if not self._predicates["debuggerskip"]:
925 910 return False
926 911
927 912 return self._cachable_skip(frame)
928 913
929 914 @lru_cache
930 915 def _cachable_skip(self, frame):
931 916 # if frame is tagged, skip by default.
932 917 if DEBUGGERSKIP in frame.f_code.co_varnames:
933 918 return True
934 919
935 920 # if one of the parent frame value set to True skip as well.
936 921
937 922 cframe = frame
938 923 while getattr(cframe, "f_back", None):
939 924 cframe = cframe.f_back
940 925 if self._get_frame_locals(cframe).get(DEBUGGERSKIP):
941 926 return True
942 927
943 928 return False
944 929
945 930 def stop_here(self, frame):
946 931 if self._is_in_decorator_internal_and_should_skip(frame) is True:
947 932 return False
948 933
949 934 hidden = False
950 935 if self.skip_hidden:
951 936 hidden = self._hidden_predicate(frame)
952 937 if hidden:
953 938 if self.report_skipped:
954 939 Colors = self.color_scheme_table.active_colors
955 940 ColorsNormal = Colors.Normal
956 941 print(
957 942 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
958 943 )
959 944 return super().stop_here(frame)
960 945
961 946 def do_up(self, arg):
962 947 """u(p) [count]
963 948 Move the current frame count (default one) levels up in the
964 949 stack trace (to an older frame).
965 950
966 951 Will skip hidden frames.
967 952 """
968 953 # modified version of upstream that skips
969 954 # frames with __tracebackhide__
970 955 if self.curindex == 0:
971 956 self.error("Oldest frame")
972 957 return
973 958 try:
974 959 count = int(arg or 1)
975 960 except ValueError:
976 961 self.error("Invalid frame count (%s)" % arg)
977 962 return
978 963 skipped = 0
979 964 if count < 0:
980 965 _newframe = 0
981 966 else:
982 967 counter = 0
983 968 hidden_frames = self.hidden_frames(self.stack)
984 969 for i in range(self.curindex - 1, -1, -1):
985 970 if hidden_frames[i] and self.skip_hidden:
986 971 skipped += 1
987 972 continue
988 973 counter += 1
989 974 if counter >= count:
990 975 break
991 976 else:
992 977 # if no break occurred.
993 978 self.error(
994 979 "all frames above hidden, use `skip_hidden False` to get get into those."
995 980 )
996 981 return
997 982
998 983 Colors = self.color_scheme_table.active_colors
999 984 ColorsNormal = Colors.Normal
1000 985 _newframe = i
1001 986 self._select_frame(_newframe)
1002 987 if skipped:
1003 988 print(
1004 989 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1005 990 )
1006 991
1007 992 def do_down(self, arg):
1008 993 """d(own) [count]
1009 994 Move the current frame count (default one) levels down in the
1010 995 stack trace (to a newer frame).
1011 996
1012 997 Will skip hidden frames.
1013 998 """
1014 999 if self.curindex + 1 == len(self.stack):
1015 1000 self.error("Newest frame")
1016 1001 return
1017 1002 try:
1018 1003 count = int(arg or 1)
1019 1004 except ValueError:
1020 1005 self.error("Invalid frame count (%s)" % arg)
1021 1006 return
1022 1007 if count < 0:
1023 1008 _newframe = len(self.stack) - 1
1024 1009 else:
1025 1010 counter = 0
1026 1011 skipped = 0
1027 1012 hidden_frames = self.hidden_frames(self.stack)
1028 1013 for i in range(self.curindex + 1, len(self.stack)):
1029 1014 if hidden_frames[i] and self.skip_hidden:
1030 1015 skipped += 1
1031 1016 continue
1032 1017 counter += 1
1033 1018 if counter >= count:
1034 1019 break
1035 1020 else:
1036 1021 self.error(
1037 1022 "all frames below hidden, use `skip_hidden False` to get get into those."
1038 1023 )
1039 1024 return
1040 1025
1041 1026 Colors = self.color_scheme_table.active_colors
1042 1027 ColorsNormal = Colors.Normal
1043 1028 if skipped:
1044 1029 print(
1045 1030 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1046 1031 )
1047 1032 _newframe = i
1048 1033
1049 1034 self._select_frame(_newframe)
1050 1035
1051 1036 do_d = do_down
1052 1037 do_u = do_up
1053 1038
1054 1039 def do_context(self, context):
1055 1040 """context number_of_lines
1056 1041 Set the number of lines of source code to show when displaying
1057 1042 stacktrace information.
1058 1043 """
1059 1044 try:
1060 1045 new_context = int(context)
1061 1046 if new_context <= 0:
1062 1047 raise ValueError()
1063 1048 self.context = new_context
1064 1049 except ValueError:
1065 1050 self.error("The 'context' command requires a positive integer argument.")
1066 1051
1067 1052
1068 1053 class InterruptiblePdb(Pdb):
1069 1054 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1070 1055
1071 1056 def cmdloop(self, intro=None):
1072 1057 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1073 1058 try:
1074 1059 return OldPdb.cmdloop(self, intro=intro)
1075 1060 except KeyboardInterrupt:
1076 1061 self.stop_here = lambda frame: False
1077 1062 self.do_quit("")
1078 1063 sys.settrace(None)
1079 1064 self.quitting = False
1080 1065 raise
1081 1066
1082 1067 def _cmdloop(self):
1083 1068 while True:
1084 1069 try:
1085 1070 # keyboard interrupts allow for an easy way to cancel
1086 1071 # the current command, so allow them during interactive input
1087 1072 self.allow_kbdint = True
1088 1073 self.cmdloop()
1089 1074 self.allow_kbdint = False
1090 1075 break
1091 1076 except KeyboardInterrupt:
1092 1077 self.message('--KeyboardInterrupt--')
1093 1078 raise
1094 1079
1095 1080
1096 1081 def set_trace(frame=None, header=None):
1097 1082 """
1098 1083 Start debugging from `frame`.
1099 1084
1100 1085 If frame is not specified, debugging starts from caller's frame.
1101 1086 """
1102 1087 pdb = Pdb()
1103 1088 if header is not None:
1104 1089 pdb.message(header)
1105 1090 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