From b21733394bbc0c69c61679723e5828b431b4ed8a Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Fri, 19 Feb 2016 18:59:53 -0500 Subject: [PATCH 1/9] not much --- xonsh/pyghooks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xonsh/pyghooks.py b/xonsh/pyghooks.py index 353ee15cf..726f44ab1 100644 --- a/xonsh/pyghooks.py +++ b/xonsh/pyghooks.py @@ -93,12 +93,13 @@ def partial_color_tokenize(template): colon = ':' expl = '!' color = Color.NO_COLOR + fg = bg = None value = '' toks = [] for literal, field, spec, conv in formatter.parse(template): if field in KNOWN_COLORS: value += literal - next_color = getattr(Color, field) + next_color, fg, bg = getattr(Color, field) if next_color is not color: if len(value) > 0: toks.append((color, value)) From 16f059d1202ba05cd5d22d2f55aaf8e127d3bd52 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Fri, 19 Feb 2016 22:00:52 -0500 Subject: [PATCH 2/9] more pygments color handling --- xonsh/pyghooks.py | 91 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 2 deletions(-) diff --git a/xonsh/pyghooks.py b/xonsh/pyghooks.py index 726f44ab1..e6e0ed75e 100644 --- a/xonsh/pyghooks.py +++ b/xonsh/pyghooks.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- """Hooks for pygments syntax highlighting.""" +import re import string from warnings import warn from collections import ChainMap +from collections.abc import MutableMapping from pygments.lexer import inherit, bygroups, using, this from pygments.token import (Keyword, Name, Comment, String, Error, Number, @@ -82,6 +84,56 @@ XonshSubprocLexer.tokens['root'] = [ Color = Token.Color # alias to new color token namespace +RE_BACKGROUND = re.compile('(BG#|BGHEX|BACKGROUND)') + +def norm_name(name): + """Normalizes a color name.""" + return name.replace('#', 'HEX').replace('BGHEX', 'BACKGROUND_') + +def color_by_name(name, fg=None, bg=None): + """Converts a color name to a color token, foreground name, + and background name. Will take into consideration current foreground + and background colors, if provided. + + Parameters + ---------- + name : str + Color name. + fg : str, optional + Foreground color name. + bg : str, optional + Background color name. + + Returns + ------- + tok : Token + Pygments Token.Color subclass + fg : str or None + New computed foreground color name. + bg : str or None + New computed background color name. + """ + name = name.upper() + if name == 'NO_COLOR': + return Color.NO_COLOR, None, None + m = RE_BACKGROUND.search(name) + if m is None: # must be foreground color + fg = norm_name(name) + else: + bg = norm_name(name) + # assmble token + if fg is None and bg is None: + tokname = 'NO_COLOR' + elif fg is None: + tokname = bg + elif bg is None: + tokname = fg + else: + tokname = fg + '__' + bg + tok = getattr(Color, tokname) + return tok, fg, bg + + def partial_color_tokenize(template): """Toeknizes a template string containing colors. Will return a list of tuples mapping the token to the string which has that color. @@ -99,7 +151,7 @@ def partial_color_tokenize(template): for literal, field, spec, conv in formatter.parse(template): if field in KNOWN_COLORS: value += literal - next_color, fg, bg = getattr(Color, field) + next_color, fg, bg = color_by_name(field, fg, bg) if next_color is not color: if len(value) > 0: toks.append((color, value)) @@ -121,6 +173,40 @@ def partial_color_tokenize(template): return toks +class CompoundColorMap(MutableMapping): + """Looks up color tokes by name, potentailly generating the value + from the lookup. + """ + + def __init__(self, styles, *args, **kwargs): + self.styles = styles + self.colors = dict(*args, **kwargs) + + def __getitem__(self, key): + if key in self.colors: + return self.colors[key] + if key in self.styles: + value = self.styles[key] + self[key] = value + return value + _, _, name = str(key).upper().rpartition('.') + value = code_by_name(name) + self[key] = value + return value + + def __setitem__(self, key, value): + self.colors[key] = value + + def __delitem__(self, key): + del self.colors[key] + + def __iter__(self): + yield from self.colors.keys() + + def __len__(self): + return len(self.colors) + + class XonshStyle(Style): """A xonsh pygments style that will dispatch to the correct color map by using a ChainMap. The style_name property may be used to reset @@ -157,7 +243,8 @@ class XonshStyle(Style): smap = get_style_by_name(value)().styles except (ImportError, pygments.util.ClassNotFound): smap = XONSH_BASE_STYLE - self.styles = ChainMap(self.trap, cmap, PTK_STYLE, smap) + compound = CompoundColorMap(ChainMap(self.trap, cmap, PTK_STYLE, smap)) + self.styles = ChainMap(self.trap, cmap, PTK_STYLE, smap, compound) self._style_name = value @style_name.deleter From f8ea79606decce562064a8d0a5ee51ce0e9a1c78 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Fri, 19 Feb 2016 23:29:07 -0500 Subject: [PATCH 3/9] more pygments color handling --- xonsh/pyghooks.py | 69 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/xonsh/pyghooks.py b/xonsh/pyghooks.py index e6e0ed75e..a17e2ce07 100644 --- a/xonsh/pyghooks.py +++ b/xonsh/pyghooks.py @@ -2,6 +2,7 @@ """Hooks for pygments syntax highlighting.""" import re import string +import builtins from warnings import warn from collections import ChainMap from collections.abc import MutableMapping @@ -88,7 +89,7 @@ RE_BACKGROUND = re.compile('(BG#|BGHEX|BACKGROUND)') def norm_name(name): """Normalizes a color name.""" - return name.replace('#', 'HEX').replace('BGHEX', 'BACKGROUND_') + return name.replace('#', 'HEX').replace('BGHEX', 'BACKGROUND_HEX') def color_by_name(name, fg=None, bg=None): """Converts a color name to a color token, foreground name, @@ -134,12 +135,62 @@ def color_by_name(name, fg=None, bg=None): return tok, fg, bg +def code_by_name(name, styles): + """Converts a token name into a pygments-style color code. + + Parameters + ---------- + name : str + Color token name. + styles : Mapping + Mapping for looking up non-hex colors + + Returns + ------- + code : str + Pygments style color code. + """ + fg, _, bg = name.lower().partition('__') + if fg.startswith('background_'): + fg, bg = bg, fg + codes = [] + # foreground color + if len(fg) == 0: + pass + elif 'hex' in fg: + for p in fg.split('_'): + codes.append('#'+p[3:] if p.startswith('hex') else p) + else: + fgtok = getattr(Color, fg.upper()) + if fgtok in styles: + codes.append(styles[fgtok]) + else: + codes += fg.split('_') + # background color + if len(bg) == 0: + pass + elif bg.startswith('background_hex'): + codes.append('bg:#'+bg[14:]) + else: + bgtok = getattr(Color, bg.upper()) + if bgtok in styles: + codes.append(styles[bgtok]) + else: + codes.append(bg.replace('background_', 'bg:')) + code = ' '.join(codes) + return code + + def partial_color_tokenize(template): """Toeknizes a template string containing colors. Will return a list of tuples mapping the token to the string which has that color. These sub-strings maybe templates themselves. """ formatter = string.Formatter() + if hasattr(builtins, '__xonsh_shell__'): + styles = __xonsh_shell__.shell.styler.styles + else: + styles = None bopen = '{' bclose = '}' colon = ':' @@ -149,12 +200,16 @@ def partial_color_tokenize(template): value = '' toks = [] for literal, field, spec, conv in formatter.parse(template): - if field in KNOWN_COLORS: + if field is None: + value += literal + elif field in KNOWN_COLORS or '#' in field: value += literal next_color, fg, bg = color_by_name(field, fg, bg) if next_color is not color: if len(value) > 0: toks.append((color, value)) + if styles is not None: + styles[color] # ensure color is available color = next_color value = '' elif field is not None: @@ -170,6 +225,8 @@ def partial_color_tokenize(template): else: value += literal toks.append((color, value)) + if styles is not None: + styles[color] # ensure color is available return toks @@ -189,8 +246,12 @@ class CompoundColorMap(MutableMapping): value = self.styles[key] self[key] = value return value - _, _, name = str(key).upper().rpartition('.') - value = code_by_name(name) + if key is Color: + raise KeyError + pre, _, name = str(key).rpartition('.') + if pre != 'Token.Color': + raise KeyError + value = code_by_name(name, self.styles) self[key] = value return value From 46ea2e589ed2c03e709b5e08333cf8064fdd5baa Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 20 Feb 2016 01:17:51 -0500 Subject: [PATCH 4/9] added hex colors to ansi --- xonsh/ansi_colors.py | 308 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 307 insertions(+), 1 deletion(-) diff --git a/xonsh/ansi_colors.py b/xonsh/ansi_colors.py index 30cec9da5..d77451e45 100644 --- a/xonsh/ansi_colors.py +++ b/xonsh/ansi_colors.py @@ -1,7 +1,10 @@ """Tools for helping with ANSI color codes.""" +import re import string from warnings import warn +RE_BACKGROUND = re.compile('(bg|bg#|bghex|background)') + def partial_color_format(template, style='default', cmap=None, hide=False): """Formats a template string but only with respect to the colors. Another template string is returned, with the color values filled in. @@ -42,8 +45,23 @@ def partial_color_format(template, style='default', cmap=None, hide=False): toks = [] for literal, field, spec, conv in formatter.parse(template): toks.append(literal) - if field in cmap: + if field is None: + pass + elif field in cmap: toks.extend([esc, cmap[field], m]) + elif '#' in field: + field = field.lower() + pre, _, post = field.partition('#') + f_or_b = '38' if RE_BACKGROUND.search(pre) is None else '48' + rgb, _, post = post.partition('_') + c256, _ = rgb_to_256(rgb) + color = f_or_b + ';5;' + c256 + mods = pre + '_' + post + if 'underline' in mods: + color = '4;' + color + if 'bold' in mods: + color = '1;' + color + toks.extend([esc, color, m]) elif field is not None: toks.append(bopen) toks.append(field) @@ -56,6 +74,294 @@ def partial_color_format(template, style='default', cmap=None, hide=False): toks.append(bclose) return ''.join(toks) +RGB_256 = { + '000000': '16', + '00005f': '17', + '000080': '04', + '000087': '18', + '0000af': '19', + '0000d7': '20', + '0000ff': '21', + '005f00': '22', + '005f5f': '23', + '005f87': '24', + '005faf': '25', + '005fd7': '26', + '005fff': '27', + '008000': '02', + '008080': '06', + '008700': '28', + '00875f': '29', + '008787': '30', + '0087af': '31', + '0087d7': '32', + '0087ff': '33', + '00af00': '34', + '00af5f': '35', + '00af87': '36', + '00afaf': '37', + '00afd7': '38', + '00afff': '39', + '00d700': '40', + '00d75f': '41', + '00d787': '42', + '00d7af': '43', + '00d7d7': '44', + '00d7ff': '45', + '00ff00': '46', + '00ff5f': '47', + '00ff87': '48', + '00ffaf': '49', + '00ffd7': '50', + '00ffff': '51', + '080808': '232', + '121212': '233', + '1c1c1c': '234', + '262626': '235', + '303030': '236', + '3a3a3a': '237', + '444444': '238', + '4e4e4e': '239', + '585858': '240', + '5f0000': '52', + '5f005f': '53', + '5f0087': '54', + '5f00af': '55', + '5f00d7': '56', + '5f00ff': '57', + '5f5f00': '58', + '5f5f5f': '59', + '5f5f87': '60', + '5f5faf': '61', + '5f5fd7': '62', + '5f5fff': '63', + '5f8700': '64', + '5f875f': '65', + '5f8787': '66', + '5f87af': '67', + '5f87d7': '68', + '5f87ff': '69', + '5faf00': '70', + '5faf5f': '71', + '5faf87': '72', + '5fafaf': '73', + '5fafd7': '74', + '5fafff': '75', + '5fd700': '76', + '5fd75f': '77', + '5fd787': '78', + '5fd7af': '79', + '5fd7d7': '80', + '5fd7ff': '81', + '5fff00': '82', + '5fff5f': '83', + '5fff87': '84', + '5fffaf': '85', + '5fffd7': '86', + '5fffff': '87', + '626262': '241', + '6c6c6c': '242', + '767676': '243', + '800000': '01', + '800080': '05', + '808000': '03', + '808080': '244', + '870000': '88', + '87005f': '89', + '870087': '90', + '8700af': '91', + '8700d7': '92', + '8700ff': '93', + '875f00': '94', + '875f5f': '95', + '875f87': '96', + '875faf': '97', + '875fd7': '98', + '875fff': '99', + '878700': '100', + '87875f': '101', + '878787': '102', + '8787af': '103', + '8787d7': '104', + '8787ff': '105', + '87af00': '106', + '87af5f': '107', + '87af87': '108', + '87afaf': '109', + '87afd7': '110', + '87afff': '111', + '87d700': '112', + '87d75f': '113', + '87d787': '114', + '87d7af': '115', + '87d7d7': '116', + '87d7ff': '117', + '87ff00': '118', + '87ff5f': '119', + '87ff87': '120', + '87ffaf': '121', + '87ffd7': '122', + '87ffff': '123', + '8a8a8a': '245', + '949494': '246', + '9e9e9e': '247', + 'a8a8a8': '248', + 'af0000': '124', + 'af005f': '125', + 'af0087': '126', + 'af00af': '127', + 'af00d7': '128', + 'af00ff': '129', + 'af5f00': '130', + 'af5f5f': '131', + 'af5f87': '132', + 'af5faf': '133', + 'af5fd7': '134', + 'af5fff': '135', + 'af8700': '136', + 'af875f': '137', + 'af8787': '138', + 'af87af': '139', + 'af87d7': '140', + 'af87ff': '141', + 'afaf00': '142', + 'afaf5f': '143', + 'afaf87': '144', + 'afafaf': '145', + 'afafd7': '146', + 'afafff': '147', + 'afd700': '148', + 'afd75f': '149', + 'afd787': '150', + 'afd7af': '151', + 'afd7d7': '152', + 'afd7ff': '153', + 'afff00': '154', + 'afff5f': '155', + 'afff87': '156', + 'afffaf': '157', + 'afffd7': '158', + 'afffff': '159', + 'b2b2b2': '249', + 'bcbcbc': '250', + 'c0c0c0': '07', + 'c6c6c6': '251', + 'd0d0d0': '252', + 'd70000': '160', + 'd7005f': '161', + 'd70087': '162', + 'd700af': '163', + 'd700d7': '164', + 'd700ff': '165', + 'd75f00': '166', + 'd75f5f': '167', + 'd75f87': '168', + 'd75faf': '169', + 'd75fd7': '170', + 'd75fff': '171', + 'd78700': '172', + 'd7875f': '173', + 'd78787': '174', + 'd787af': '175', + 'd787d7': '176', + 'd787ff': '177', + 'd7af00': '178', + 'd7af5f': '179', + 'd7af87': '180', + 'd7afaf': '181', + 'd7afd7': '182', + 'd7afff': '183', + 'd7d700': '184', + 'd7d75f': '185', + 'd7d787': '186', + 'd7d7af': '187', + 'd7d7d7': '188', + 'd7d7ff': '189', + 'd7ff00': '190', + 'd7ff5f': '191', + 'd7ff87': '192', + 'd7ffaf': '193', + 'd7ffd7': '194', + 'd7ffff': '195', + 'dadada': '253', + 'e4e4e4': '254', + 'eeeeee': '255', + 'ff0000': '196', + 'ff005f': '197', + 'ff0087': '198', + 'ff00af': '199', + 'ff00d7': '200', + 'ff00ff': '201', + 'ff5f00': '202', + 'ff5f5f': '203', + 'ff5f87': '204', + 'ff5faf': '205', + 'ff5fd7': '206', + 'ff5fff': '207', + 'ff8700': '208', + 'ff875f': '209', + 'ff8787': '210', + 'ff87af': '211', + 'ff87d7': '212', + 'ff87ff': '213', + 'ffaf00': '214', + 'ffaf5f': '215', + 'ffaf87': '216', + 'ffafaf': '217', + 'ffafd7': '218', + 'ffafff': '219', + 'ffd700': '220', + 'ffd75f': '221', + 'ffd787': '222', + 'ffd7af': '223', + 'ffd7d7': '224', + 'ffd7ff': '225', + 'ffff00': '226', + 'ffff5f': '227', + 'ffff87': '228', + 'ffffaf': '229', + 'ffffd7': '230', + 'ffffff': '231', +} + +RE_RGB3 = re.compile(r'(.)(.)(.)') +RE_RGB6 = re.compile(r'(..)(..)(..)') + +def rgb_to_ints(rgb): + """Converts an RGB string into a tuple of ints.""" + if len(rgb) == 6: + return tuple([int(h, 16) for h in RE_RGB6.split(rgb)[1:4]]) + else: + return tuple([int(h*2, 16) for h in RE_RGB3.split(rgb)[1:4]]) + + +def rgb_to_256(rgb): + """Find the closest ANSI 256 approximation to the given RGB value. + Thanks to Micah Elliott (http://MicahElliott.com) for colortrans.py + """ + rgb = rgb.lstrip('#') + if len(rgb) == 0: + return '0', '000000' + incs = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff) + # Break 6-char RGB code into 3 integer vals. + parts = rgb_to_ints(rgb) + res = [] + for part in parts: + i = 0 + while i < len(incs)-1: + s, b = incs[i], incs[i+1] # smaller, bigger + if s <= part <= b: + s1 = abs(s - part) + b1 = abs(b - part) + if s1 < b1: closest = s + else: closest = b + res.append(closest) + break + i += 1 + res = ''.join([('%02.x' % i) for i in res]) + equiv = RGB_256[res] + return equiv, res + DEFAULT_STYLE = { # Reset From 2edef145886f42ffd8dfdd2e80c65cdb8bd955f9 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 20 Feb 2016 02:47:12 -0500 Subject: [PATCH 5/9] hilarious --- xonsh/aliases.py | 8 +++++++- xonsh/mplhooks.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 xonsh/mplhooks.py diff --git a/xonsh/aliases.py b/xonsh/aliases.py index d321e665d..1f6b95c18 100644 --- a/xonsh/aliases.py +++ b/xonsh/aliases.py @@ -75,7 +75,7 @@ def source_foreign(args, stdin=None): """Sources a file written in a foreign shell language.""" parser = _ensure_source_foreign_parser() ns = parser.parse_args(args) - if ns.prevcmd is not None: + if ns.prevcmd is not None: pass # don't change prevcmd if given explicitly elif os.path.isfile(ns.files_or_code[0]): # we have filename to source @@ -191,6 +191,11 @@ def vox(args, stdin=None): vox = Vox() return vox(args, stdin=stdin) +def mpl(args, stdin=None): + """Hooks to matplotlib""" + from xonsh.mplhooks import show + show() + DEFAULT_ALIASES = { 'cd': cd, @@ -212,6 +217,7 @@ DEFAULT_ALIASES = { 'replay': replay_main, '!!': bang_bang, '!n': bang_n, + 'mpl': mpl, 'trace': trace, 'timeit': timeit_alias, 'xonfig': xonfig, diff --git a/xonsh/mplhooks.py b/xonsh/mplhooks.py new file mode 100644 index 000000000..3aa0ebdb7 --- /dev/null +++ b/xonsh/mplhooks.py @@ -0,0 +1,51 @@ +"""Matplotlib hooks, for what its worth.""" +import shutil + +import numpy as np +import matplotlib.pyplot as plt + +from xonsh.tools import print_color + +def figure_to_rgb_array(fig, width, height): + """Converts figure to a numpy array of rgb values + + Forked from http://www.icare.univ-lille1.fr/wiki/index.php/How_to_convert_a_matplotlib_figure_to_a_numpy_array_or_a_PIL_image + """ + w, h = fig.canvas.get_width_height() + dpi = fig.get_dpi() + fig.set_size_inches(width/dpi, height/dpi, forward=True) + width, height = fig.canvas.get_width_height() + # draw the renderer + fig.canvas.draw() + + # Get the RGB buffer from the figure + buf = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8) + buf.shape = (height, width, 3) + return buf + + +def buf_to_color_str(buf): + """Converts an RGB array to a xonsh color string.""" + pix = '{{bg#{0:02x}{1:02x}{2:02x}}} ' + pixels = [] + for h in range(buf.shape[0]): + last = None + for w in range(buf.shape[1]): + rgb = buf[h,w] + if last is not None and (last == rgb).all(): + pixels.append(' ') + else: + pixels.append(pix.format(*rgb)) + last = rgb + pixels.append('{NO_COLOR}\n') + pixels[-1] = pixels[-1].rstrip() + return ''.join(pixels) + + +def show(): + fig = plt.gcf() + w, h = shutil.get_terminal_size() + h -= 1 # leave space for next prompt + buf = figure_to_rgb_array(fig, w, h) + s = buf_to_color_str(buf) + print_color(s) From c2689bcb08cc08d651d58f9935918c6558ae0a74 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 20 Feb 2016 14:53:00 -0500 Subject: [PATCH 6/9] some test fixes --- tests/tools.py | 6 +++++- xonsh/mplhooks.py | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/tools.py b/tests/tools.py index fc90e6b07..94be04e9e 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -6,6 +6,7 @@ import glob import builtins import platform import subprocess +from collections import defaultdict from contextlib import contextmanager from nose.plugins.skip import SkipTest @@ -23,10 +24,13 @@ ON_MAC = (platform.system() == 'Darwin') def sp(cmd): return subprocess.check_output(cmd, universal_newlines=True) +class DummyStyler(): + styles = defaultdict(None.__class__) + class DummyBaseShell(BaseShell): def __init__(self): - pass + self.styler = DummyStyler() class DummyShell: diff --git a/xonsh/mplhooks.py b/xonsh/mplhooks.py index 3aa0ebdb7..4831c6f54 100644 --- a/xonsh/mplhooks.py +++ b/xonsh/mplhooks.py @@ -2,6 +2,7 @@ import shutil import numpy as np +import matplotlib import matplotlib.pyplot as plt from xonsh.tools import print_color @@ -15,17 +16,28 @@ def figure_to_rgb_array(fig, width, height): dpi = fig.get_dpi() fig.set_size_inches(width/dpi, height/dpi, forward=True) width, height = fig.canvas.get_width_height() - # draw the renderer - fig.canvas.draw() + ax = fig.gca() + ax.set_xticklabels([]) + ax.set_yticklabels([]) + fig.set_tight_layout(True) + fig.set_frameon(False) + fig.set_facecolor('w') + font_size = matplotlib.rcParams['font.size'] + matplotlib.rcParams.update({'font.size': 1}) - # Get the RGB buffer from the figure + # Draw the renderer and get the RGB buffer from the figure + fig.canvas.draw() buf = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8) buf.shape = (height, width, 3) + + # clean up and return + matplotlib.rcParams.update({'font.size': font_size}) return buf def buf_to_color_str(buf): """Converts an RGB array to a xonsh color string.""" + space = ' ' pix = '{{bg#{0:02x}{1:02x}{2:02x}}} ' pixels = [] for h in range(buf.shape[0]): @@ -33,7 +45,7 @@ def buf_to_color_str(buf): for w in range(buf.shape[1]): rgb = buf[h,w] if last is not None and (last == rgb).all(): - pixels.append(' ') + pixels.append(space) else: pixels.append(pix.format(*rgb)) last = rgb From 0b88ad7375cb3a560e42f02027887e7b6c3ab0db Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sat, 20 Feb 2016 15:24:19 -0500 Subject: [PATCH 7/9] added some more color docs --- docs/tutorial.rst | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 7e5057b2b..a0c92d632 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -875,6 +875,7 @@ keyword arguments, which will be replaced automatically: snail@home:~ >> # so does that! By default, the following variables are available for use: + * ``user``: The username of the current user * ``hostname``: The name of the host computer * ``cwd``: The current working directory @@ -895,13 +896,35 @@ By default, the following variables are available for use: You can also color your prompt easily by inserting keywords such as ``{GREEN}`` or ``{BOLD_BLUE}``. Colors have the form shown below: - * ``(QUALIFIER\_)COLORNAME``: Inserts an ANSI color code - * ``COLORNAME`` can be any of: ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, - ``BLUE``, ``PURPLE``, ``CYAN``, or ``WHITE`` - * ``QUALIFIER`` is optional and can be any of: ``BOLD``, ``UNDERLINE``, - ``BACKGROUND``, ``INTENSE``, ``BOLD_INTENSE``, or - ``BACKGROUND_INTENSE`` - * ``NO_COLOR``: Resets any previously used color codes +* ``NO_COLOR``: Resets any previously used color codes +* ``COLORNAME``: Inserts a color code for the following basic colors, + which come in regular (dark) and intense (light) forms: + + - ``BLACK`` or ``INTENSE_BLACK`` + - ``RED`` or ``INTENSE_RED`` + - ``GREEN`` or ``INTENSE_GREEN`` + - ``YELLOW`` or ``INTENSE_YELLOW`` + - ``BLUE`` or ``INTENSE_BLUE`` + - ``PURPLE`` or ``INTENSE_PURPLE`` + - ``CYAN`` or ``INTENSE_CYAN`` + - ``WHITE`` or ``INTENSE_WHITE`` + +* ``#HEX``: A ``#`` before a len-3 or len-6 hex code will use that + hex color, or the nearest approximation that that is supported by + the shell and terminal. For example, ``#fff`` and ``#fafad2`` are + both valid. +* ``BACKGROUND_`` may be added to the begining of a color name or hex + color to set a background color. For example, ``BACKGROUND_INTENSE_RED`` + and ``BACKGROUND_#123456`` can both be used. +* ``bg#HEX`` or ``BG#HEX`` are shortcuts for setting a background hex color. + Thus you can set ``bg#0012ab`` or the uppercase version. +* ``BOLD_`` is a prefix qualifier that may be used with any foreground color. + For example, ``BOLD_RED`` and ``BOLD_#112233`` are OK! +* ``UNDERLINE_`` is a prefix qualifier that also may be used with any + foreground color. For example, ``UNDERLINE_GREEN``. +* Or any other combination of qualifiers, such as + ``BOLD_UNDERLINE_INTENSE_BLACK``, which is the most metal color you + can use! You can make use of additional variables beyond these by adding them to the ``FORMATTER_DICT`` environment variable. The values in this dictionary From c9ebd33abf2bc3be173dc90d583493d96bfde281 Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Sun, 21 Feb 2016 13:00:01 -0500 Subject: [PATCH 8/9] rprompt implementation --- CHANGELOG.rst | 2 ++ xonsh/environ.py | 6 ++++++ xonsh/ptk/shell.py | 15 +++++++++++++++ xonsh/ptk/shortcuts.py | 5 +++++ xonsh/shell.py | 6 ++++++ 5 files changed, 34 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 03a9f8857..a24af790e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,8 @@ Current Developments * ``?`` and ``??`` operator output now has colored titles, like in IPython. * ``??`` will syntax highlight source code if pygments is available. * Python mode output is now syntax highlighted if pygments is available. +* New ``$RIGHT_PROMPT`` environment variable for displaying right-aligned + text in prompt-toolkit shell. **Changed:** diff --git a/xonsh/environ.py b/xonsh/environ.py index 60efd060b..497582646 100644 --- a/xonsh/environ.py +++ b/xonsh/environ.py @@ -79,6 +79,7 @@ DEFAULT_ENSURERS = { re.compile('\w*PATH$'): (is_env_path, str_to_env_path, env_path_to_str), 'PATHEXT': (is_env_path, str_to_env_path, env_path_to_str), 'RAISE_SUBPROC_ERROR': (is_bool, to_bool, bool_to_str), + 'RIGHT_PROMPT': (is_string, ensure_string, ensure_string), 'TEEPTY_PIPE_DELAY': (is_float, float, str), 'XONSHRC': (is_env_path, str_to_env_path, env_path_to_str), 'XONSH_COLOR_STYLE': (is_string, ensure_string, ensure_string), @@ -181,6 +182,7 @@ DEFAULT_VALUES = { 'PUSHD_MINUS': False, 'PUSHD_SILENT': False, 'RAISE_SUBPROC_ERROR': False, + 'RIGHT_PROMPT': '', 'SHELL_TYPE': 'best', 'SUGGEST_COMMANDS': True, 'SUGGEST_MAX_NUM': 5, @@ -349,6 +351,10 @@ DEFAULT_DOCS = { 'This is most useful in xonsh scripts or modules where failures ' 'should cause an end to execution. This is less useful at a terminal.' 'The error that is raised is a subprocess.CalledProcessError.'), + 'RIGHT_PROMPT': VarDocs('Template string for right-aligned text ' + 'at the prompt. This may be parameterized in the same way as ' + 'the $PROMPT variable. Currently, this is only available in the ' + 'prompt-toolkit shell.'), 'SHELL_TYPE': VarDocs( 'Which shell is used. Currently two base shell types are supported:\n\n' " - 'readline' that is backed by Python's readline module\n" diff --git a/xonsh/ptk/shell.py b/xonsh/ptk/shell.py index 0d6c2e689..4ce9d06aa 100644 --- a/xonsh/ptk/shell.py +++ b/xonsh/ptk/shell.py @@ -63,6 +63,7 @@ class PromptToolkitShell(BaseShell): mouse_support=mouse_support, auto_suggest=auto_suggest, get_prompt_tokens=self.prompt_tokens, + get_rprompt_tokens=self.rprompt_tokens, style=PygmentsStyle(xonsh_style_proxy(self.styler)), completer=completer, lexer=PygmentsLexer(XonshLexer), @@ -127,6 +128,20 @@ class PromptToolkitShell(BaseShell): self.settitle() return toks + def rprompt_tokens(self, cli): + """Returns a list of (token, str) tuples for the current right + prompt. + """ + p = builtins.__xonsh_env__.get('RIGHT_PROMPT') + if len(p) == 0: + return [] + try: + p = partial_format_prompt(p) + except Exception: # pylint: disable=broad-except + print_exception() + toks = partial_color_tokenize(p) + return toks + def format_color(self, string, **kwargs): """Formats a color string using Pygments. This, therefore, returns a list of (Token, str) tuples. diff --git a/xonsh/ptk/shortcuts.py b/xonsh/ptk/shortcuts.py index 156ef732c..e7cac0aec 100644 --- a/xonsh/ptk/shortcuts.py +++ b/xonsh/ptk/shortcuts.py @@ -4,6 +4,8 @@ from prompt_toolkit.utils import DummyContext from prompt_toolkit.shortcuts import (create_prompt_application, create_eventloop, create_asyncio_eventloop, create_output) +from xonsh.shell import prompt_toolkit_version_info + class Prompter(object): def __init__(self, cli=None, *args, **kwargs): @@ -18,6 +20,7 @@ class Prompter(object): will be created when the prompt() method is called. """ self.cli = cli + self.major_minor = prompt_toolkit_version_info()[:2] def __enter__(self): self.reset() @@ -61,6 +64,8 @@ class Prompter(object): # Create CommandLineInterface. if self.cli is None: + if self.major_minor <= (0, 57): + kwargs.pop('get_rprompt_tokens', None) cli = CommandLineInterface( application=create_prompt_application(message, **kwargs), eventloop=eventloop, diff --git a/xonsh/shell.py b/xonsh/shell.py index 5fb34fec7..0ccbf68a7 100644 --- a/xonsh/shell.py +++ b/xonsh/shell.py @@ -33,6 +33,12 @@ def prompt_toolkit_version(): return getattr(prompt_toolkit, '__version__', '<0.57') +def prompt_toolkit_version_info(): + """Gets the prompt toolkit version info tuple.""" + v = prompt_toolkit_version().strip('<>+-=.') + return tuple(map(int, v.split('.'))) + + def best_shell_type(): """Gets the best shell type that is available""" if ON_WINDOWS or is_prompt_toolkit_available(): From 3476e72098c44c756d0cc7be900c6bf6ca24b0ac Mon Sep 17 00:00:00 2001 From: Anthony Scopatz Date: Mon, 22 Feb 2016 23:25:09 -0500 Subject: [PATCH 9/9] potential fix for ptk promot slowdown --- xonsh/ptk/shell.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xonsh/ptk/shell.py b/xonsh/ptk/shell.py index 0d6c2e689..98cb7c0e7 100644 --- a/xonsh/ptk/shell.py +++ b/xonsh/ptk/shell.py @@ -58,11 +58,13 @@ class PromptToolkitShell(BaseShell): multicolumn = (completions_display == 'multi') self.styler.style_name = env.get('XONSH_COLOR_STYLE') completer = None if completions_display == 'none' else self.pt_completer + prompt_tokens = self.prompt_tokens(None) + get_prompt_tokens = lambda cli: prompt_tokens with self.prompter: line = self.prompter.prompt( mouse_support=mouse_support, auto_suggest=auto_suggest, - get_prompt_tokens=self.prompt_tokens, + get_prompt_tokens=get_prompt_tokens, style=PygmentsStyle(xonsh_style_proxy(self.styler)), completer=completer, lexer=PygmentsLexer(XonshLexer),