1# -*- coding: utf-8 -*-
2"""Tools for coloring text in ANSI terminals.
3"""
4
5#*****************************************************************************
6#       Copyright (C) 2002-2006 Fernando Perez. <fperez@colorado.edu>
7#
8#  Distributed under the terms of the BSD License.  The full license is in
9#  the file COPYING, distributed as part of this software.
10#*****************************************************************************
11
12__all__ = ['TermColors','InputTermColors','ColorScheme','ColorSchemeTable']
13
14import os
15
16from IPython.utils.ipstruct import Struct
17
18color_templates = (
19        # Dark colors
20        ("Black"       , "0;30"),
21        ("Red"         , "0;31"),
22        ("Green"       , "0;32"),
23        ("Brown"       , "0;33"),
24        ("Blue"        , "0;34"),
25        ("Purple"      , "0;35"),
26        ("Cyan"        , "0;36"),
27        ("LightGray"   , "0;37"),
28        # Light colors
29        ("DarkGray"    , "1;30"),
30        ("LightRed"    , "1;31"),
31        ("LightGreen"  , "1;32"),
32        ("Yellow"      , "1;33"),
33        ("LightBlue"   , "1;34"),
34        ("LightPurple" , "1;35"),
35        ("LightCyan"   , "1;36"),
36        ("White"       , "1;37"),
37        # Blinking colors.  Probably should not be used in anything serious.
38        ("BlinkBlack"  , "5;30"),
39        ("BlinkRed"    , "5;31"),
40        ("BlinkGreen"  , "5;32"),
41        ("BlinkYellow" , "5;33"),
42        ("BlinkBlue"   , "5;34"),
43        ("BlinkPurple" , "5;35"),
44        ("BlinkCyan"   , "5;36"),
45        ("BlinkLightGray", "5;37"),
46        )
47
48def make_color_table(in_class):
49    """Build a set of color attributes in a class.
50
51    Helper function for building the :class:`TermColors` and
52    :class`InputTermColors`.
53    """
54    for name,value in color_templates:
55        setattr(in_class,name,in_class._base % value)
56
57class TermColors:
58    """Color escape sequences.
59
60    This class defines the escape sequences for all the standard (ANSI?)
61    colors in terminals. Also defines a NoColor escape which is just the null
62    string, suitable for defining 'dummy' color schemes in terminals which get
63    confused by color escapes.
64
65    This class should be used as a mixin for building color schemes."""
66
67    NoColor = ''  # for color schemes in color-less terminals.
68    Normal = '\033[0m'   # Reset normal coloring
69    _base  = '\033[%sm'  # Template for all other colors
70
71# Build the actual color table as a set of class attributes:
72make_color_table(TermColors)
73
74class InputTermColors:
75    """Color escape sequences for input prompts.
76
77    This class is similar to TermColors, but the escapes are wrapped in \001
78    and \002 so that readline can properly know the length of each line and
79    can wrap lines accordingly.  Use this class for any colored text which
80    needs to be used in input prompts, such as in calls to raw_input().
81
82    This class defines the escape sequences for all the standard (ANSI?)
83    colors in terminals. Also defines a NoColor escape which is just the null
84    string, suitable for defining 'dummy' color schemes in terminals which get
85    confused by color escapes.
86
87    This class should be used as a mixin for building color schemes."""
88
89    NoColor = ''  # for color schemes in color-less terminals.
90
91    if os.name == 'nt' and os.environ.get('TERM','dumb') == 'emacs':
92        # (X)emacs on W32 gets confused with \001 and \002 so we remove them
93        Normal = '\033[0m'   # Reset normal coloring
94        _base  = '\033[%sm'  # Template for all other colors
95    else:
96        Normal = '\001\033[0m\002'   # Reset normal coloring
97        _base  = '\001\033[%sm\002'  # Template for all other colors
98
99# Build the actual color table as a set of class attributes:
100make_color_table(InputTermColors)
101
102class NoColors:
103    """This defines all the same names as the colour classes, but maps them to
104    empty strings, so it can easily be substituted to turn off colours."""
105    NoColor = ''
106    Normal  = ''
107
108for name, value in color_templates:
109    setattr(NoColors, name, '')
110
111class ColorScheme:
112    """Generic color scheme class. Just a name and a Struct."""
113    def __init__(self,__scheme_name_,colordict=None,**colormap):
114        self.name = __scheme_name_
115        if colordict is None:
116            self.colors = Struct(**colormap)
117        else:
118            self.colors = Struct(colordict)
119
120    def copy(self,name=None):
121        """Return a full copy of the object, optionally renaming it."""
122        if name is None:
123            name = self.name
124        return ColorScheme(name, self.colors.dict())
125
126class ColorSchemeTable(dict):
127    """General class to handle tables of color schemes.
128
129    It's basically a dict of color schemes with a couple of shorthand
130    attributes and some convenient methods.
131
132    active_scheme_name -> obvious
133    active_colors -> actual color table of the active scheme"""
134
135    def __init__(self, scheme_list=None, default_scheme=''):
136        """Create a table of color schemes.
137
138        The table can be created empty and manually filled or it can be
139        created with a list of valid color schemes AND the specification for
140        the default active scheme.
141        """
142
143        # create object attributes to be set later
144        self.active_scheme_name = ''
145        self.active_colors = None
146
147        if scheme_list:
148            if default_scheme == '':
149                raise ValueError('you must specify the default color scheme')
150            for scheme in scheme_list:
151                self.add_scheme(scheme)
152            self.set_active_scheme(default_scheme)
153
154    def copy(self):
155        """Return full copy of object"""
156        return ColorSchemeTable(self.values(),self.active_scheme_name)
157
158    def add_scheme(self,new_scheme):
159        """Add a new color scheme to the table."""
160        if not isinstance(new_scheme,ColorScheme):
161            raise ValueError('ColorSchemeTable only accepts ColorScheme instances')
162        self[new_scheme.name] = new_scheme
163
164    def set_active_scheme(self,scheme,case_sensitive=0):
165        """Set the currently active scheme.
166
167        Names are by default compared in a case-insensitive way, but this can
168        be changed by setting the parameter case_sensitive to true."""
169
170        scheme_names = list(self.keys())
171        if case_sensitive:
172            valid_schemes = scheme_names
173            scheme_test = scheme
174        else:
175            valid_schemes = [s.lower() for s in scheme_names]
176            scheme_test = scheme.lower()
177        try:
178            scheme_idx = valid_schemes.index(scheme_test)
179        except ValueError:
180            raise ValueError('Unrecognized color scheme: ' + scheme + \
181                  '\nValid schemes: '+str(scheme_names).replace("'', ",''))
182        else:
183            active = scheme_names[scheme_idx]
184            self.active_scheme_name = active
185            self.active_colors = self[active].colors
186            # Now allow using '' as an index for the current active scheme
187            self[''] = self[active]
188