import curses import itertools colors = {} _colors = [] _pairs = {} index = 1 attributes = { 'bold': curses.A_BOLD, 'reverse': curses.A_REVERSE, 'normal': curses.A_NORMAL, 'underline': curses.A_UNDERLINE, 'dim': curses.A_DIM, 'standout': curses.A_STANDOUT, } inited = False default_color = True ascii_map = { '*': 'bold', } rev_ascii_map = { 'bold': '*', } # add a particular color def add_color(name, value, abbrev): colors[name] = value ascii_map[abbrev] = name rev_ascii_map[name] = abbrev # assign every RGB triple (0-5) to one of six basic colors (red, yellow, green, # cyan, blue, magenta) based on some semi-arbitrary rules i came up with. def iter256(): ns = list(range(0, 6)) for r in ns: for g in ns: for b in ns: if r >= g and g > b: name = 'yellow' elif r >= b and b - g > 1: name = 'magenta' elif b >= g and g - r > 2: name = 'cyan' elif b >= r and r > g: name = 'magenta' elif g >= r and r - b > 2: name = 'yellow' elif g >= b and b - r > 1: name = 'cyan' elif r > b and r > g: name = 'red' elif g > b and g > r: name = 'green' elif b > r and b > g: name = 'blue' else: continue yield (name, r, g, b) return # if we can support 256 colors, create all of the color names and map them to # the correct RGB value. def color256(name, fallback, abbrev, r, g, b): name2 = '%s%d%d%d' % (name, r, g, b) abbrev2 = '%s%d%d%d' % (abbrev, r, g, b) if curses.COLORS == 256: value = 16 + r * 36 + g * 6 + b if curses.can_change_color(): curses.init_color(value, r * 200, g * 200, b* 200) else: value = fallback add_color(name2, value, abbrev2) def init(): global _pairs, inited if inited: return inited = True # create the basic 8 colors of curses add_color('cyan', curses.COLOR_CYAN, 'c') add_color('blue', curses.COLOR_BLUE, 'b') add_color('green', curses.COLOR_GREEN, 'g') add_color('red', curses.COLOR_RED, 'r') add_color('yellow', curses.COLOR_YELLOW, 'y') add_color('magenta', curses.COLOR_MAGENTA, 'm') add_color('black', curses.COLOR_BLACK, 'B') add_color('white', curses.COLOR_WHITE, 'w') # initialize the "default" color if possible if default_color: colors['default'] = -1 ascii_map['d'] = 'default' rev_ascii_map['default'] = 'd' # add in hex aliases to 256 colors; used by color-data buffers for i in range(0, 256): name = 'f%02x' % i abbrev = 'f%02x' % i add_color(name, i, abbrev) # create the 24 flavors of grey in 256 colors for i in range(0, 24): name2 = 'grey%d' % i abbrev2 = 'G%d' % i if curses.COLORS == 256: value = 232 + i if curses.can_change_color(): curses.init_color(value, i * 41, i * 41, i * 41) else: value = curses.COLOR_WHITE add_color(name2, value, abbrev2) # create 256 colors for name, r, g, b in iter256(): color256(name, colors[name], rev_ascii_map[name], r, g, b) def build(fg, bg, *attr): v = curses.A_NORMAL | pairs(fg, bg) for x in attr: if type(x) == type(''): x = attributes[x] v = v | x return v def pairs(fg, bg): if not curses.has_colors(): return curses.color_pair(0) global index fgi = colors.get(fg, colors['white']) bgi = colors.get(bg, colors['black']) key = (fgi, bgi) if key not in _pairs: assert index < curses.COLOR_PAIRS, "too many colors" assert type(fgi) == type(0), "illegal fgi: %r" % fgi assert type(bgi) == type(0), "illegal bgi: %r" % bgi try: curses.init_pair(index, fgi, bgi) except: raise Exception('failed to init %d,%d,%d' % (index, fgi, bgi)) _pairs[key] = curses.color_pair(index) _colors.append(key) index = len(_colors) + 1 return _pairs[key]