coloransi.py
246 lines
| 8.6 KiB
| text/x-python
|
PythonLexer
|
r0 | """Tools for coloring text in ANSI terminals. | ||
|
r1853 | """ | ||
|
r0 | |||
#***************************************************************************** | ||||
|
r88 | # Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu> | ||
|
r0 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#***************************************************************************** | ||||
import os | ||||
|
r28595 | import warnings | ||
|
r0 | |||
|
r2030 | from IPython.utils.ipstruct import Struct | ||
|
r0 | |||
|
r28587 | __all__ = ["TermColors", "InputTermColors", "ColorScheme", "ColorSchemeTable"] | ||
|
r28596 | _sentinel = object() | ||
|
r5495 | color_templates = ( | ||
|
r1593 | # Dark colors | ||
|
r0 | ("Black" , "0;30"), | ||
("Red" , "0;31"), | ||||
("Green" , "0;32"), | ||||
("Brown" , "0;33"), | ||||
("Blue" , "0;34"), | ||||
("Purple" , "0;35"), | ||||
("Cyan" , "0;36"), | ||||
("LightGray" , "0;37"), | ||||
|
r1593 | # Light colors | ||
|
r0 | ("DarkGray" , "1;30"), | ||
("LightRed" , "1;31"), | ||||
("LightGreen" , "1;32"), | ||||
("Yellow" , "1;33"), | ||||
("LightBlue" , "1;34"), | ||||
("LightPurple" , "1;35"), | ||||
("LightCyan" , "1;36"), | ||||
|
r4872 | ("White" , "1;37"), | ||
|
r1593 | # Blinking colors. Probably should not be used in anything serious. | ||
("BlinkBlack" , "5;30"), | ||||
("BlinkRed" , "5;31"), | ||||
("BlinkGreen" , "5;32"), | ||||
("BlinkYellow" , "5;33"), | ||||
("BlinkBlue" , "5;34"), | ||||
("BlinkPurple" , "5;35"), | ||||
("BlinkCyan" , "5;36"), | ||||
("BlinkLightGray", "5;37"), | ||||
) | ||||
|
r1589 | |||
|
r5495 | def make_color_table(in_class): | ||
"""Build a set of color attributes in a class. | ||||
|
r13592 | Helper function for building the :class:`TermColors` and | ||
:class`InputTermColors`. | ||||
""" | ||||
|
r1594 | for name,value in color_templates: | ||
setattr(in_class,name,in_class._base % value) | ||||
|
r1589 | |||
|
r0 | class TermColors: | ||
"""Color escape sequences. | ||||
|
r4872 | This class defines the escape sequences for all the standard (ANSI?) | ||
|
r0 | colors in terminals. Also defines a NoColor escape which is just the null | ||
string, suitable for defining 'dummy' color schemes in terminals which get | ||||
confused by color escapes. | ||||
This class should be used as a mixin for building color schemes.""" | ||||
|
r4872 | |||
|
r0 | NoColor = '' # for color schemes in color-less terminals. | ||
Normal = '\033[0m' # Reset normal coloring | ||||
|
r28723 | |||
Black = "\033[0;30m" | ||||
Red = "\033[0;31m" | ||||
Green = "\033[0;32m" | ||||
Brown = "\033[0;33m" | ||||
Blue = "\033[0;34m" | ||||
Purple = "\033[0;35m" | ||||
Cyan = "\033[0;36m" | ||||
LightGray = "\033[0;37m" | ||||
|
r28711 | # Light colors | ||
|
r28723 | DarkGray = "\033[1;30m" | ||
LightRed = "\033[1;31m" | ||||
LightGreen = "\033[1;32m" | ||||
Yellow = "\033[1;33m" | ||||
LightBlue = "\033[1;34m" | ||||
LightPurple = "\033[1;35m" | ||||
LightCyan = "\033[1;36m" | ||||
White = "\033[1;37m" | ||||
|
r28711 | # Blinking colors. Probably should not be used in anything serious. | ||
|
r28723 | BlinkBlack = "\033[5;30m" | ||
BlinkRed = "\033[5;31m" | ||||
BlinkGreen = "\033[5;32m" | ||||
BlinkYellow = "\033[5;33m" | ||||
BlinkBlue = "\033[5;34m" | ||||
BlinkPurple = "\033[5;35m" | ||||
BlinkCyan = "\033[5;36m" | ||||
BlinkLightGray = "\033[5;37m" | ||||
|
r28711 | |||
|
r0 | |||
class InputTermColors: | ||||
"""Color escape sequences for input prompts. | ||||
|
r27688 | This class is similar to TermColors, but the escapes are wrapped in \\001 | ||
and \\002 so that readline can properly know the length of each line and | ||||
|
r0 | can wrap lines accordingly. Use this class for any colored text which | ||
needs to be used in input prompts, such as in calls to raw_input(). | ||||
|
r4872 | This class defines the escape sequences for all the standard (ANSI?) | ||
|
r0 | colors in terminals. Also defines a NoColor escape which is just the null | ||
string, suitable for defining 'dummy' color schemes in terminals which get | ||||
confused by color escapes. | ||||
This class should be used as a mixin for building color schemes.""" | ||||
|
r4872 | |||
|
r0 | NoColor = '' # for color schemes in color-less terminals. | ||
|
r550 | |||
|
r564 | if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs': | ||
|
r550 | # (X)emacs on W32 gets confused with \001 and \002 so we remove them | ||
Normal = '\033[0m' # Reset normal coloring | ||||
_base = '\033[%sm' # Template for all other colors | ||||
else: | ||||
Normal = '\001\033[0m\002' # Reset normal coloring | ||||
_base = '\001\033[%sm\002' # Template for all other colors | ||||
|
r0 | |||
# Build the actual color table as a set of class attributes: | ||||
make_color_table(InputTermColors) | ||||
|
r5495 | class NoColors: | ||
"""This defines all the same names as the colour classes, but maps them to | ||||
empty strings, so it can easily be substituted to turn off colours.""" | ||||
NoColor = '' | ||||
|
r7580 | Normal = '' | ||
|
r5495 | |||
for name, value in color_templates: | ||||
setattr(NoColors, name, '') | ||||
|
r0 | class ColorScheme: | ||
"""Generic color scheme class. Just a name and a Struct.""" | ||||
|
r28587 | |||
name: str | ||||
colors: Struct | ||||
|
r28595 | def __init__(self, __scheme_name_, colordict=None, **colormap): | ||
|
r0 | self.name = __scheme_name_ | ||
|
r28595 | if colormap: | ||
warnings.warn( | ||||
"Passing each colors as a kwarg to ColorScheme is " | ||||
"considered for deprecation. Please pass a " | ||||
|
r28605 | "a single dict as second parameter. If you are using this" | ||
"feature, please comment an subscribe to issue " | ||||
|
r28595 | "https://github.com/ipython/ipython/issues/14304", | ||
PendingDeprecationWarning, | ||||
stacklevel=2, | ||||
) | ||||
|
r0 | if colordict is None: | ||
self.colors = Struct(**colormap) | ||||
else: | ||||
self.colors = Struct(colordict) | ||||
|
r46 | |||
def copy(self,name=None): | ||||
"""Return a full copy of the object, optionally renaming it.""" | ||||
if name is None: | ||||
name = self.name | ||||
|
r2077 | return ColorScheme(name, self.colors.dict()) | ||
|
r4872 | |||
|
r46 | class ColorSchemeTable(dict): | ||
|
r0 | """General class to handle tables of color schemes. | ||
It's basically a dict of color schemes with a couple of shorthand | ||||
attributes and some convenient methods. | ||||
|
r4872 | |||
|
r0 | active_scheme_name -> obvious | ||
active_colors -> actual color table of the active scheme""" | ||||
|
r21778 | def __init__(self, scheme_list=None, default_scheme=''): | ||
|
r0 | """Create a table of color schemes. | ||
The table can be created empty and manually filled or it can be | ||||
created with a list of valid color schemes AND the specification for | ||||
the default active scheme. | ||||
""" | ||||
|
r4872 | |||
|
r46 | # create object attributes to be set later | ||
self.active_scheme_name = '' | ||||
self.active_colors = None | ||||
|
r4872 | |||
|
r46 | if scheme_list: | ||
|
r0 | if default_scheme == '': | ||
|
r7843 | raise ValueError('you must specify the default color scheme') | ||
|
r0 | for scheme in scheme_list: | ||
self.add_scheme(scheme) | ||||
self.set_active_scheme(default_scheme) | ||||
|
r46 | def copy(self): | ||
"""Return full copy of object""" | ||||
return ColorSchemeTable(self.values(),self.active_scheme_name) | ||||
|
r28596 | def __setitem__(self, key: str, value: ColorScheme): | ||
assert isinstance(key, str) | ||||
assert isinstance(value, ColorScheme) | ||||
super().__setitem__(key, value) | ||||
def add_scheme(self, new_scheme): | ||||
|
r0 | """Add a new color scheme to the table.""" | ||
|
r28596 | if not isinstance(new_scheme, ColorScheme): | ||
|
r7843 | raise ValueError('ColorSchemeTable only accepts ColorScheme instances') | ||
|
r0 | self[new_scheme.name] = new_scheme | ||
|
r4872 | |||
|
r28596 | def set_active_scheme(self, scheme, case_sensitive=_sentinel): | ||
|
r0 | """Set the currently active scheme. | ||
Names are by default compared in a case-insensitive way, but this can | ||||
be changed by setting the parameter case_sensitive to true.""" | ||||
|
r28596 | if case_sensitive is _sentinel: | ||
case_sensitive = False | ||||
else: | ||||
warnings.warn( | ||||
"set_active_scheme(case_sensitive=...) is Pending " | ||||
"deprecation. Please comment on " | ||||
"https://github.com/ipython/ipython/issues/14306 " | ||||
"to let the ipython maintainer that you are affected.", | ||||
PendingDeprecationWarning, | ||||
stacklevel=2, | ||||
) | ||||
|
r13358 | scheme_names = list(self.keys()) | ||
|
r0 | if case_sensitive: | ||
|
r46 | valid_schemes = scheme_names | ||
|
r0 | scheme_test = scheme | ||
else: | ||||
|
r46 | valid_schemes = [s.lower() for s in scheme_names] | ||
|
r0 | scheme_test = scheme.lower() | ||
try: | ||||
scheme_idx = valid_schemes.index(scheme_test) | ||||
|
r25833 | except ValueError as e: | ||
|
r7843 | raise ValueError('Unrecognized color scheme: ' + scheme + \ | ||
|
r25833 | '\nValid schemes: '+str(scheme_names).replace("'', ",'')) from e | ||
|
r0 | else: | ||
|
r46 | active = scheme_names[scheme_idx] | ||
|
r0 | self.active_scheme_name = active | ||
self.active_colors = self[active].colors | ||||
# Now allow using '' as an index for the current active scheme | ||||
self[''] = self[active] | ||||