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