Merge pull request #687 from scopatz/c

Color Refactor
This commit is contained in:
Gil Forsyth 2016-02-18 17:19:06 -05:00
commit b330ff277d
15 changed files with 5070 additions and 374 deletions

View file

@ -8,14 +8,38 @@ Current Developments
* Added new valid ``$SHELL_TYPE`` called ``'best'``. This selects the best value
for the concrete shell type based on the availability on the user's machine.
* New environment variable ``$XONSH_COLOR_STYLE`` will set the color mapping
for all of xonsh.
* New ``XonshStyle`` pygments style will determine the approriate color
mapping based on ``$XONSH_COLOR_STYLE``. The associated ``xonsh_style_proxy()``
is intended for wrapping ``XonshStyle`` when actually being used by
pygments.
* The functions ``print_color()`` and ``format_color()`` found in ``xonsh.tools``
dispatch to the approriate shell color handling and may be used from
anywhere.
* ``xonsh.tools.HAVE_PYGMENTS`` flag now denotes if pygments is installed and
available on the users system.
* The ``ansi_colors`` module is now availble for handling ANSI color codes.
* ``?`` 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.
**Changed:**
* Updated ``$SHELL_TYPE`` default to ``'best'``.
* Shell classes are now responsible for implementing their own color
formatting and printing.
* Prompt coloring, history diffing, and tracing uses new color handling
capabilities.
* New ``Token.Color`` token for xonsh color names, e.g. we now use
``Token.Color.RED`` rather than ``Token.RED``.
**Deprecated:** None
**Removed:** None
**Removed:**
* The ``xonsh.tools.TERM_COLORS`` mapping has been axed, along with all
references to it.
**Fixed:**

View file

@ -76,22 +76,28 @@ def test_repath_home_var_brace():
assert_equal(exp, obs[0])
def test_helper_int():
helper(int, 'int')
with mock_xonsh_env({}):
helper(int, 'int')
def test_helper_helper():
helper(helper, 'helper')
with mock_xonsh_env({}):
helper(helper, 'helper')
def test_helper_env():
helper(Env, 'Env')
with mock_xonsh_env({}):
helper(Env, 'Env')
def test_superhelper_int():
superhelper(int, 'int')
with mock_xonsh_env({}):
superhelper(int, 'int')
def test_superhelper_helper():
superhelper(helper, 'helper')
with mock_xonsh_env({}):
superhelper(helper, 'helper')
def test_superhelper_env():
superhelper(Env, 'Env')
with mock_xonsh_env({}):
superhelper(Env, 'Env')
def test_ensure_list_of_strs():
cases = [(['yo'], 'yo'), (['yo'], ['yo']), (['42'], 42), (['42'], [42])]

View file

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
"""Tests some tools function for prompt_toolkit integration."""
from __future__ import unicode_literals, print_function
import nose
from nose.tools import assert_equal
import builtins
from xonsh.tools import format_prompt_for_prompt_toolkit
from xonsh.tools import TERM_COLORS
from xonsh.environ import format_prompt, Env
builtins.__xonsh_env__ = Env()
builtins.__xonsh_env__['PROMPT_TOOLKIT_COLORS'] = {'WHITE': '#f0f0f0'}
def test_format_prompt_for_prompt_toolkit():
templ = ('>>> {BOLD_INTENSE_BLUE}~/xonsh {WHITE} {BACKGROUND_RED} {INTENSE_RED}(main){NO_COLOR}')
prompt = format_prompt(templ, TERM_COLORS)
token_names, color_styles, strings = format_prompt_for_prompt_toolkit(prompt)
assert_equal(token_names, ['NO_COLOR', 'BOLD_INTENSE_BLUE', 'WHITE', 'BACKGROUND_RED', 'INTENSE_RED', 'NO_COLOR'])
assert_equal(color_styles, ['noinherit', 'bold #0000d2', '#f0f0f0', 'bg:#800000', 'bg:#800000 #ff1010', 'noinherit'])
assert_equal(strings, ['>>> ', '~/xonsh ', ' ', ' ', '(main)', ''])
if __name__ == '__main__':
nose.runmodule()

View file

@ -11,6 +11,7 @@ from contextlib import contextmanager
from nose.plugins.skip import SkipTest
from xonsh.built_ins import ensure_list_of_strs
from xonsh.base_shell import BaseShell
VER_3_4 = (3, 4)
@ -22,10 +23,25 @@ ON_MAC = (platform.system() == 'Darwin')
def sp(cmd):
return subprocess.check_output(cmd, universal_newlines=True)
class DummyBaseShell(BaseShell):
def __init__(self):
pass
class DummyShell:
def settitle():
pass
_shell = None
@property
def shell(self):
if self._shell is None:
self._shell = DummyBaseShell()
return self._shell
@contextmanager
def mock_xonsh_env(xenv):
builtins.__xonsh_env__ = xenv

2260
xonsh/ansi_colors.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -7,9 +7,11 @@ import time
import builtins
from xonsh.tools import XonshError, escape_windows_title_string, ON_WINDOWS, \
print_exception, format_color
print_exception, HAVE_PYGMENTS
from xonsh.completer import Completer
from xonsh.environ import multiline_prompt, format_prompt
if HAVE_PYGMENTS:
from xonsh.pyghooks import XonshStyle
class _TeeOut(object):
@ -30,7 +32,7 @@ class _TeeOut(object):
def write(self, data):
"""Writes data to the original stdout and the buffer."""
data = data.replace('\001', '').replace('\002', '')
#data = data.replace('\001', '').replace('\002', '')
self.stdout.write(data)
self.buffer.write(data)
@ -63,7 +65,7 @@ class _TeeErr(object):
def write(self, data):
"""Writes data to the original stderr and the buffer."""
data = data.replace('\001', '').replace('\002', '')
#data = data.replace('\001', '').replace('\002', '')
self.stderr.write(data)
self.buffer.write(data)
@ -113,6 +115,11 @@ class BaseShell(object):
self.buffer = []
self.need_more_lines = False
self.mlprompt = None
if HAVE_PYGMENTS:
env = builtins.__xonsh_env__
self.styler = XonshStyle(env.get('XONSH_COLOR_STYLE'))
else:
self.styler = None
def emptyline(self):
"""Called when an empty line has been entered."""
@ -250,8 +257,16 @@ class BaseShell(object):
hist.append(info)
hist.last_cmd_rtn = hist.last_cmd_out = None
def print_color(self, string, **kwargs):
"""Prints a string in color. This base implmentation uses ANSI
color codes.
def format_color(self, string, **kwargs):
"""Formats the colors in a string. This base implmentation does not
actually do any coloring, but just returns the string directly.
"""
print(format_color(string), **kwargs)
return string
def print_color(self, string, **kwargs):
"""Prints a string in color. This base implmentation does not actually
do any coloring, but just prints the string directly.
"""
if not isinstance(string, str):
string = ''.join([x for _, x in string])
print(string, **kwargs)

View file

@ -5,13 +5,13 @@ from itertools import zip_longest
from difflib import SequenceMatcher
from xonsh import lazyjson
from xonsh.tools import TERM_COLORS
from xonsh.tools import print_color, format_color
NO_COLOR = TERM_COLORS['NO_COLOR'].replace('\001', '').replace('\002', '')
RED = TERM_COLORS['RED'].replace('\001', '').replace('\002', '')
GREEN = TERM_COLORS['GREEN'].replace('\001', '').replace('\002', '')
BOLD_RED = TERM_COLORS['BOLD_RED'].replace('\001', '').replace('\002', '')
BOLD_GREEN = TERM_COLORS['BOLD_GREEN'].replace('\001', '').replace('\002', '')
NO_COLOR = '{NO_COLOR}'
RED = '{RED}'
GREEN = '{GREEN}'
BOLD_RED = '{BOLD_RED}'
BOLD_GREEN = '{BOLD_GREEN}'
# intern some strings
REPLACE = 'replace'
@ -299,7 +299,7 @@ def _create_parser(p=None):
def _main_action(ns, hist=None):
hd = HistoryDiffer(ns.a, ns.b, reopen=ns.reopen, verbose=ns.verbose)
print(hd.format())
print_color(hd.format())
def main(args=None, stdin=None):

View file

@ -17,7 +17,7 @@ from collections import MutableMapping, MutableSequence, MutableSet, namedtuple
from xonsh import __version__ as XONSH_VERSION
from xonsh.tools import (
TERM_COLORS, ON_WINDOWS, ON_MAC, ON_LINUX, ON_ARCH, IS_ROOT,
ON_WINDOWS, ON_MAC, ON_LINUX, ON_ARCH, IS_ROOT,
always_true, always_false, ensure_string, is_env_path, str_to_env_path,
env_path_to_str, is_bool, to_bool, bool_to_str, is_history_tuple, to_history_tuple,
history_tuple_to_str, is_float, string_types, is_string, DEFAULT_ENCODING,
@ -81,6 +81,7 @@ DEFAULT_ENSURERS = {
'RAISE_SUBPROC_ERROR': (is_bool, to_bool, bool_to_str),
'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),
'XONSH_ENCODING': (is_string, ensure_string, ensure_string),
'XONSH_ENCODING_ERRORS': (is_string, ensure_string, ensure_string),
'XONSH_HISTORY_SIZE': (is_history_tuple, to_history_tuple, history_tuple_to_str),
@ -195,6 +196,7 @@ DEFAULT_VALUES = {
'xonsh', 'xonshrc'),
os.path.expanduser('~/.xonshrc')) if ON_WINDOWS
else ('/etc/xonshrc', os.path.expanduser('~/.xonshrc'))),
'XONSH_COLOR_STYLE': 'default',
'XONSH_CONFIG_DIR': xonsh_config_dir,
'XONSH_DATA_DIR': xonsh_data_dir,
'XONSH_ENCODING': DEFAULT_ENCODING,
@ -408,6 +410,9 @@ DEFAULT_DOCS = {
'control file if there is a naming collision.', default=(
"On Linux & Mac OSX: ('/etc/xonshrc', '~/.xonshrc')\n"
"On Windows: ('%ALLUSERSPROFILE%\\xonsh\\xonshrc', '~/.xonshrc')")),
'XONSH_COLOR_STYLE': VarDocs(
'Sets the color style for xonsh colors. This is a style name, not '
'a color map.'),
'XONSH_CONFIG_DIR': VarDocs(
'This is the location where xonsh configuration information is stored.',
configurable=False, default="'$XDG_CONFIG_HOME/xonsh'"),
@ -882,14 +887,14 @@ def dirty_working_directory(cwd=None):
def branch_color():
"""Return red if the current branch is dirty, otherwise green"""
return (TERM_COLORS['BOLD_INTENSE_RED'] if dirty_working_directory() else
TERM_COLORS['BOLD_INTENSE_GREEN'])
return ('{BOLD_INTENSE_RED}' if dirty_working_directory() else
'{BOLD_INTENSE_GREEN}')
def branch_bg_color():
"""Return red if the current branch is dirty, otherwise green"""
return (TERM_COLORS['BACKGROUND_RED'] if dirty_working_directory() else
TERM_COLORS['BACKGROUND_GREEN'])
return ('{BACKGROUND_RED}' if dirty_working_directory() else
'{BACKGROUND_GREEN}')
def _replace_home(x):
@ -961,7 +966,7 @@ FORMATTER_DICT = dict(
branch_bg_color=branch_bg_color,
current_job=_current_job,
env_name=env_name,
**TERM_COLORS)
)
DEFAULT_VALUES['FORMATTER_DICT'] = dict(FORMATTER_DICT)
_FORMATTER = string.Formatter()
@ -983,13 +988,18 @@ def is_template_string(template, formatter_dict=None):
return included_names <= known_names
def format_prompt(template=DEFAULT_PROMPT, formatter_dict=None):
"""Formats a xonsh prompt template string."""
template = template() if callable(template) else template
def _get_fmtter(formatter_dict=None):
if formatter_dict is None:
fmtter = builtins.__xonsh_env__.get('FORMATTER_DICT', FORMATTER_DICT)
else:
fmtter = formatter_dict
return fmtter
def format_prompt(template=DEFAULT_PROMPT, formatter_dict=None):
"""Formats a xonsh prompt template string."""
template = template() if callable(template) else template
fmtter = _get_fmtter(formatter_dict)
included_names = set(i[1] for i in _FORMATTER.parse(template))
fmt = {}
for name in included_names:
@ -1005,12 +1015,47 @@ def format_prompt(template=DEFAULT_PROMPT, formatter_dict=None):
return template.format(**fmt)
def partial_format_prompt(template=DEFAULT_PROMPT, formatter_dict=None):
"""Formats a xonsh prompt template string."""
template = template() if callable(template) else template
fmtter = _get_fmtter(formatter_dict)
bopen = '{'
bclose = '}'
colon = ':'
expl = '!'
toks = []
for literal, field, spec, conv in _FORMATTER.parse(template):
toks.append(literal)
if field is None:
continue
elif field.startswith('$'):
v = builtins.__xonsh_env__[name[1:]]
v = _FORMATTER.convert_field(v, conv)
v = _FORMATTER.format_field(v, spec)
toks.append(v)
continue
elif field in fmtter:
v = fmtter[field]
val = v() if callable(v) else v
val = '' if val is None else val
toks.append(val)
else:
toks.append(bopen)
toks.append(field)
if conv is not None and len(conv) > 0:
toks.append(expl)
toks.append(conv)
if spec is not None and len(spec) > 0:
toks.append(colon)
toks.append(spec)
toks.append(bclose)
return ''.join(toks)
RE_HIDDEN = re.compile('\001.*?\002')
def multiline_prompt():
def multiline_prompt(curr=''):
"""Returns the filler text for the prompt in multiline scenarios."""
curr = builtins.__xonsh_env__.get('PROMPT')
curr = format_prompt(curr)
line = curr.rsplit('\n', 1)[1] if '\n' in curr else curr
line = RE_HIDDEN.sub('', line) # gets rid of colors
# most prompts end in whitespace, head is the part before that.

View file

@ -17,8 +17,12 @@ import io as stdlib_io
from collections import namedtuple
from xonsh import openpy
from xonsh.tools import (cast_unicode, safe_hasattr, string_types, indent,
VER_MAJOR_MINOR, VER_3_4)
from xonsh.tools import (cast_unicode, safe_hasattr, string_types, indent,
VER_MAJOR_MINOR, VER_3_4, print_color, format_color, HAVE_PYGMENTS)
if HAVE_PYGMENTS:
import pygments
from xonsh import pyghooks
if sys.version_info[0] > 2:
ISPY3K = True
@ -297,7 +301,7 @@ def find_source_lines(obj):
if VER_MAJOR_MINOR <= VER_3_4:
FrameInfo = namedtuple('FrameInfo', ['frame', 'filename', 'lineno', 'function',
FrameInfo = namedtuple('FrameInfo', ['frame', 'filename', 'lineno', 'function',
'code_context', 'index'])
def getouterframes(frame, context=1):
"""Wrapper for getouterframes so that it acts like the Python v3.5 version."""
@ -420,8 +424,8 @@ class Inspector(object):
o = openpy.read_py_file(ofile, skip_encoding_cookie=False)
print(o, lineno - 1)
def _format_fields(self, fields, title_width=0):
"""Formats a list of fields for display.
def _format_fields_str(self, fields, title_width=0):
"""Formats a list of fields for display using color strings.
Parameters
----------
@ -434,12 +438,61 @@ class Inspector(object):
if title_width == 0:
title_width = max(len(title) + 2 for title, _ in fields)
for title, content in fields:
title_len = len(title)
title = '{BOLD_RED}' + title + ':{NO_COLOR}'
if len(content.splitlines()) > 1:
title = title + ":\n"
title += '\n'
else:
title = (title + ":").ljust(title_width)
title += " ".ljust(title_width - title_len)
out.append(cast_unicode(title) + cast_unicode(content))
return "\n".join(out)
return format_color("\n".join(out) + '\n')
def _format_fields_tokens(self, fields, title_width=0):
"""Formats a list of fields for display using color tokens from
pygments.
Parameters
----------
fields : list
A list of 2-tuples: (field_title, field_content)
title_width : int
How many characters to pad titles to. Default to longest title.
"""
out = []
if title_width == 0:
title_width = max(len(title) + 2 for title, _ in fields)
for title, content in fields:
title_len = len(title)
title = '{BOLD_RED}' + title + ':{NO_COLOR}'
if not isinstance(content, str) or len(content.splitlines()) > 1:
title += '\n'
else:
title += " ".ljust(title_width - title_len)
out += pyghooks.partial_color_tokenize(title)
if isinstance(content, str):
out[-1] = (out[-1][0], out[-1][1] + content + '\n')
else:
out += content
out[-1] = (out[-1][0], out[-1][1] + '\n')
out[-1] = (out[-1][0], out[-1][1] + '\n')
return out
def _format_fields(self, fields, title_width=0):
"""Formats a list of fields for display using color tokens from
pygments.
Parameters
----------
fields : list
A list of 2-tuples: (field_title, field_content)
title_width : int
How many characters to pad titles to. Default to longest title.
"""
if HAVE_PYGMENTS:
rtn = self._format_fields_tokens(fields, title_width=title_width)
else:
rtn = self._format_fields_str(fields, title_width=title_width)
return rtn
# The fields to be displayed by pinfo: (fancy_name, key_in_info_dict)
pinfo_fields1 = [("Type", "type_name")]
@ -521,7 +574,7 @@ class Inspector(object):
# Finally send to printer/pager:
if displayfields:
print(self._format_fields(displayfields))
print_color(self._format_fields(displayfields))
def info(self, obj, oname='', info=None, detail_level=0):
"""Compute a dict with detailed information about an object.
@ -635,7 +688,11 @@ class Inspector(object):
if hasattr(obj, '__class__'):
source = getsource(obj.__class__, binary_file)
if source is not None:
out['source'] = source.rstrip()
source = source.rstrip()
if HAVE_PYGMENTS:
lexer = pyghooks.XonshLexer()
source = list(pygments.lex(source, lexer=lexer))
out['source'] = source
except Exception: # pylint:disable=broad-except
pass

View file

@ -13,8 +13,14 @@ except ImportError:
from xonsh import __version__
from xonsh.shell import Shell
from xonsh.pretty import pprint
from xonsh.pretty import pprint, pretty
from xonsh.jobs import ignore_sigtstp
from xonsh.tools import HAVE_PYGMENTS, print_color
if HAVE_PYGMENTS:
import pygments
from xonsh import pyghooks
def path_argument(s):
"""Return a path only if the path is actually legal
@ -123,7 +129,13 @@ def undo_args(args):
def _pprint_displayhook(value):
if value is not None:
builtins._ = None # Set '_' to None to avoid recursion
pprint(value)
if HAVE_PYGMENTS:
s = pretty(value) # color case
lexer = pyghooks.XonshLexer()
tokens = list(pygments.lex(s, lexer=lexer))
print_color(tokens)
else:
pprint(value) # black & white case
builtins._ = value

View file

@ -14,13 +14,14 @@ from pygments.token import (Keyword, Name, Comment, String, Error, Number,
Operator, Generic, Whitespace, Token)
from xonsh.base_shell import BaseShell
from xonsh.tools import (format_prompt_for_prompt_toolkit, _make_style,
print_exception, format_color)
from xonsh.tools import print_exception, format_color
from xonsh.environ import partial_format_prompt
from xonsh.pyghooks import XonshLexer, XonshStyle, partial_color_tokenize, \
xonsh_style_proxy
from xonsh.ptk.completer import PromptToolkitCompleter
from xonsh.ptk.history import PromptToolkitHistory
from xonsh.ptk.key_bindings import load_xonsh_bindings
from xonsh.ptk.shortcuts import Prompter, print_tokens
from xonsh.pyghooks import XonshLexer
class PromptToolkitShell(BaseShell):
@ -39,13 +40,12 @@ class PromptToolkitShell(BaseShell):
enable_open_in_editor=True)
load_xonsh_bindings(self.key_bindings_manager)
def singleline(self, store_in_history=True, auto_suggest=None,
def singleline(self, store_in_history=True, auto_suggest=None,
enable_history_search=True, multiline=True, **kwargs):
"""Reads a single line of input from the shell. The store_in_history
kwarg flags whether the input should be stored in PTK's in-memory
history.
"""
token_func, style_cls = self._get_prompt_tokens_and_style()
env = builtins.__xonsh_env__
mouse_support = env.get('MOUSE_SUPPORT')
if store_in_history:
@ -56,16 +56,17 @@ class PromptToolkitShell(BaseShell):
auto_suggest = auto_suggest if env.get('AUTO_SUGGEST') else None
completions_display = env.get('COMPLETIONS_DISPLAY')
multicolumn = (completions_display == 'multi')
self.styler.style_name = env.get('XONSH_COLOR_STYLE')
completer = None if completions_display == 'none' else self.pt_completer
with self.prompter:
line = self.prompter.prompt(
mouse_support=mouse_support,
auto_suggest=auto_suggest,
get_prompt_tokens=token_func,
style=style_cls,
get_prompt_tokens=self.prompt_tokens,
style=PygmentsStyle(xonsh_style_proxy(self.styler)),
completer=completer,
lexer=PygmentsLexer(XonshLexer),
multiline=multiline,
multiline=multiline,
history=history,
enable_history_search=enable_history_search,
reserve_space_for_menu=0,
@ -115,80 +116,31 @@ class PromptToolkitShell(BaseShell):
else:
break
def _get_prompt_tokens_and_style(self):
"""Returns function to pass as prompt to prompt_toolkit."""
token_names, cstyles, strings = format_prompt_for_prompt_toolkit(self.prompt)
tokens = [getattr(Token, n) for n in token_names]
def get_tokens(cli):
return list(zip(tokens, strings))
custom_style = _xonsh_style(tokens, cstyles)
return get_tokens, custom_style
def prompt_tokens(self, cli):
"""Returns a list of (token, str) tuples for the current prompt."""
p = builtins.__xonsh_env__.get('PROMPT')
try:
p = partial_format_prompt(p)
except Exception: # pylint: disable=broad-except
print_exception()
toks = partial_color_tokenize(p)
self.settitle()
return toks
def print_color(self, string,end='\n', **kwargs):
def format_color(self, string, **kwargs):
"""Formats a color string using Pygments. This, therefore, returns
a list of (Token, str) tuples.
"""
return partial_color_tokenize(string)
def print_color(self, string, end='\n', **kwargs):
"""Prints a color string using prompt-toolkit color management."""
s = format_color(string + end, remove_escapes=False)
token_names, cstyles, strings = format_prompt_for_prompt_toolkit(s)
toks = [getattr(Token, n) for n in token_names]
custom_style = PygmentsStyle(_xonsh_style(toks, cstyles))
tokens = list(zip(toks, strings))
print_tokens(tokens, style=custom_style)
def _xonsh_style(tokens=tuple(), cstyles=tuple()):
class XonshStyle(Style):
styles = {
Whitespace: "GRAY",
Comment: "UNDERLINE INTENSE GRAY",
Comment.Preproc: "UNDERLINE INTENSE GRAY",
Keyword: "BOLD GREEN",
Keyword.Pseudo: "GREEN",
Keyword.Type: "MAGENTA",
Operator: "GRAY",
Operator.Word: "BOLD",
Name.Builtin: "INTENSE GREEN",
Name.Function: "BLUE",
Name.Class: "BOLD BLUE",
Name.Namespace: "BOLD BLUE",
Name.Exception: "BOLD INTENSE RED",
Name.Variable: "CYAN",
Name.Constant: "RED",
Name.Label: "YELLOW",
Name.Entity: "BOLD WHITE",
Name.Attribute: "CYAN",
Name.Tag: "BOLD GREEN",
Name.Decorator: "CYAN",
String: "MAGENTA",
String.Doc: "UNDERLINE MAGENTA",
String.Interpol: "BOLD MAGENTA",
String.Escape: "BOLD RED",
String.Regex: "MAGENTA",
String.Symbol: "BOLD GREEN",
String.Other: "GREEN",
Number: "RED",
Generic.Heading: "BOLD BLUE",
Generic.Subheading: "BOLD MAGENTA",
Generic.Deleted: "RED",
Generic.Inserted: "GREEN",
Generic.Error: "BOLD RED",
Generic.Emph: "UNDERLINE",
Generic.Prompt: "BOLD BLUE",
Generic.Output: "GRAY",
Generic.Traceback: "RED",
Error: "RED",
}
styles = {k: _make_style(v) for k, v in styles.items()}
styles.update({
Token.Menu.Completions.Completion.Current: 'bg:#00aaaa #000000',
Token.Menu.Completions.Completion: 'bg:#008888 #ffffff',
Token.Menu.Completions.ProgressButton: 'bg:#003333',
Token.Menu.Completions.ProgressBar: 'bg:#00aaaa',
Token.AutoSuggestion: '#666666',
Token.Aborted: '#888888',
})
# update with the prompt styles
styles.update(dict(zip(tokens, cstyles)))
# Update with with any user styles
userstyle = builtins.__xonsh_env__.get('PROMPT_TOOLKIT_STYLES')
if userstyle is not None:
styles.update(userstyle)
return XonshStyle
if isinstance(string, str):
tokens = partial_color_tokenize(string + end)
else:
# assume this is a list of (Token, str) tuples and just print
tokens = string
env = builtins.__xonsh_env__
self.styler.style_name = env.get('XONSH_COLOR_STYLE')
proxy_style = PygmentsStyle(xonsh_style_proxy(self.styler))
print_tokens(tokens, style=proxy_style)

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,14 @@ from collections import deque
from xonsh import lazyjson
from xonsh.base_shell import BaseShell
from xonsh.tools import ON_WINDOWS, print_color
from xonsh.ansi_colors import partial_color_format
from xonsh.environ import partial_format_prompt, multiline_prompt
from xonsh.tools import ON_WINDOWS, print_exception, HAVE_PYGMENTS
if HAVE_PYGMENTS:
from xonsh import pyghooks
import pygments
from pygments.formatters.terminal256 import Terminal256Formatter
RL_COMPLETION_SUPPRESS_APPEND = RL_LIB = None
RL_CAN_RESIZE = False
@ -96,6 +103,7 @@ class ReadlineShell(BaseShell, Cmd):
**kwargs)
setup_readline()
self._current_indent = ''
self._current_prompt = ''
self.cmdqueue = deque()
def __del__(self):
@ -103,7 +111,7 @@ class ReadlineShell(BaseShell, Cmd):
def singleline(self, store_in_history=True, **kwargs):
"""Reads a single line of input. The store_in_history kwarg
flags whether the input should be stored in readline's in-memory
flags whether the input should be stored in readline's in-memory
history.
"""
if not store_in_history: # store current position to remove it later
@ -225,7 +233,7 @@ class ReadlineShell(BaseShell, Cmd):
if inserter is not None:
readline.set_pre_input_hook(None)
else:
print_color(self.prompt, file=self.stdout)
self.print_color(self.prompt, file=self.stdout)
if line is not None:
os.write(self.stdin.fileno(), line.encode())
if not exec_now:
@ -268,7 +276,45 @@ class ReadlineShell(BaseShell, Cmd):
# This is needed to support some system where line-wrapping doesn't
# work. This is a bug in upstream Python, or possibly readline.
RL_LIB.rl_reset_screen_size()
return super().prompt
#return super().prompt
if self.need_more_lines:
if self.mlprompt is None:
try:
self.mlprompt = multiline_prompt(curr=self._current_prompt)
except Exception: # pylint: disable=broad-except
print_exception()
self.mlprompt = '<multiline prompt error> '
return self.mlprompt
env = builtins.__xonsh_env__ # pylint: disable=no-member
p = env.get('PROMPT')
try:
p = partial_format_prompt(p)
except Exception: # pylint: disable=broad-except
print_exception()
p = partial_color_format(p, style=env.get('XONSH_COLOR_STYLE'),
hide=True)
self._current_prompt = p
self.settitle()
return p
def format_color(self, string, hide=False, **kwargs):
"""Readline implementation of color formatting. This usesg ANSI color
codes.
"""
return partial_color_format(string, hide=hide,
style=builtins.__xonsh_env__.get('XONSH_COLOR_STYLE'))
def print_color(self, string, hide=False, **kwargs):
if isinstance(string, str):
s = self.format_color(string, hide=hide)
else:
# assume this is a list of (Token, str) tuples and format it
env = builtins.__xonsh_env__
self.styler.style_name = env.get('XONSH_COLOR_STYLE')
style_proxy = pyghooks.xonsh_style_proxy(self.styler)
formatter = Terminal256Formatter(style=style_proxy)
s = pygments.format(string, formatter).rstrip()
print(s, **kwargs)
class ReadlineHistoryAdder(Thread):
@ -307,4 +353,3 @@ class ReadlineHistoryAdder(Thread):
lj.close()
except (IOError, OSError):
continue

View file

@ -30,6 +30,19 @@ from contextlib import contextmanager
from collections import OrderedDict, Sequence, Set
from warnings import warn
#
# Check pygments
#
def pygments_version():
"""Returns the Pygments version or False."""
try:
import pygments
v = pygments.__version__
except ImportError:
v = False
return v
if sys.version_info[0] >= 3:
string_types = (str, bytes)
unicode_type = str
@ -37,6 +50,7 @@ else:
string_types = (str, unicode)
unicode_type = unicode
DEFAULT_ENCODING = sys.getdefaultencoding()
ON_WINDOWS = (platform.system() == 'Windows')
@ -45,6 +59,7 @@ ON_LINUX = (platform.system() == 'Linux')
ON_ARCH = (platform.linux_distribution()[0] == 'arch')
ON_POSIX = (os.name == 'posix')
IS_ROOT = ctypes.windll.shell32.IsUserAnAdmin() != 0 if ON_WINDOWS else os.getuid() == 0
HAVE_PYGMENTS = bool(pygments_version())
VER_3_4 = (3, 4)
VER_3_5 = (3, 5)
@ -223,74 +238,6 @@ def get_sep():
os.sep)
TERM_COLORS = {
# Reset
'NO_COLOR': '\001\033[0m\002', # Text Reset
# Regular Colors
'BLACK': '\001\033[0;30m\002', # BLACK
'RED': '\001\033[0;31m\002', # RED
'GREEN': '\001\033[0;32m\002', # GREEN
'YELLOW': '\001\033[0;33m\002', # YELLOW
'BLUE': '\001\033[0;34m\002', # BLUE
'PURPLE': '\001\033[0;35m\002', # PURPLE
'CYAN': '\001\033[0;36m\002', # CYAN
'WHITE': '\001\033[0;37m\002', # WHITE
# Bold
'BOLD_BLACK': '\001\033[1;30m\002', # BLACK
'BOLD_RED': '\001\033[1;31m\002', # RED
'BOLD_GREEN': '\001\033[1;32m\002', # GREEN
'BOLD_YELLOW': '\001\033[1;33m\002', # YELLOW
'BOLD_BLUE': '\001\033[1;34m\002', # BLUE
'BOLD_PURPLE': '\001\033[1;35m\002', # PURPLE
'BOLD_CYAN': '\001\033[1;36m\002', # CYAN
'BOLD_WHITE': '\001\033[1;37m\002', # WHITE
# Underline
'UNDERLINE_BLACK': '\001\033[4;30m\002', # BLACK
'UNDERLINE_RED': '\001\033[4;31m\002', # RED
'UNDERLINE_GREEN': '\001\033[4;32m\002', # GREEN
'UNDERLINE_YELLOW': '\001\033[4;33m\002', # YELLOW
'UNDERLINE_BLUE': '\001\033[4;34m\002', # BLUE
'UNDERLINE_PURPLE': '\001\033[4;35m\002', # PURPLE
'UNDERLINE_CYAN': '\001\033[4;36m\002', # CYAN
'UNDERLINE_WHITE': '\001\033[4;37m\002', # WHITE
# Background
'BACKGROUND_BLACK': '\001\033[40m\002', # BLACK
'BACKGROUND_RED': '\001\033[41m\002', # RED
'BACKGROUND_GREEN': '\001\033[42m\002', # GREEN
'BACKGROUND_YELLOW': '\001\033[43m\002', # YELLOW
'BACKGROUND_BLUE': '\001\033[44m\002', # BLUE
'BACKGROUND_PURPLE': '\001\033[45m\002', # PURPLE
'BACKGROUND_CYAN': '\001\033[46m\002', # CYAN
'BACKGROUND_WHITE': '\001\033[47m\002', # WHITE
# High Intensity
'INTENSE_BLACK': '\001\033[0;90m\002', # BLACK
'INTENSE_RED': '\001\033[0;91m\002', # RED
'INTENSE_GREEN': '\001\033[0;92m\002', # GREEN
'INTENSE_YELLOW': '\001\033[0;93m\002', # YELLOW
'INTENSE_BLUE': '\001\033[0;94m\002', # BLUE
'INTENSE_PURPLE': '\001\033[0;95m\002', # PURPLE
'INTENSE_CYAN': '\001\033[0;96m\002', # CYAN
'INTENSE_WHITE': '\001\033[0;97m\002', # WHITE
# Bold High Intensity
'BOLD_INTENSE_BLACK': '\001\033[1;90m\002', # BLACK
'BOLD_INTENSE_RED': '\001\033[1;91m\002', # RED
'BOLD_INTENSE_GREEN': '\001\033[1;92m\002', # GREEN
'BOLD_INTENSE_YELLOW': '\001\033[1;93m\002', # YELLOW
'BOLD_INTENSE_BLUE': '\001\033[1;94m\002', # BLUE
'BOLD_INTENSE_PURPLE': '\001\033[1;95m\002', # PURPLE
'BOLD_INTENSE_CYAN': '\001\033[1;96m\002', # CYAN
'BOLD_INTENSE_WHITE': '\001\033[1;97m\002', # WHITE
# High Intensity backgrounds
'BACKGROUND_INTENSE_BLACK': '\001\033[0;100m\002', # BLACK
'BACKGROUND_INTENSE_RED': '\001\033[0;101m\002', # RED
'BACKGROUND_INTENSE_GREEN': '\001\033[0;102m\002', # GREEN
'BACKGROUND_INTENSE_YELLOW': '\001\033[0;103m\002', # YELLOW
'BACKGROUND_INTENSE_BLUE': '\001\033[0;104m\002', # BLUE
'BACKGROUND_INTENSE_PURPLE': '\001\033[0;105m\002', # PURPLE
'BACKGROUND_INTENSE_CYAN': '\001\033[0;106m\002', # CYAN
'BACKGROUND_INTENSE_WHITE': '\001\033[0;107m\002', # WHITE
}
def fallback(cond, backup):
"""Decorator for returning the object if cond is true and a backup if cond is false.
@ -737,128 +684,19 @@ def history_tuple_to_str(x):
"""Converts a valid history tuple to a canonical string."""
return '{0} {1}'.format(*x)
#
# Check pygments
#
def pygments_version():
"""Returns the Pygments version or False."""
try:
import pygments
v = pygments.__version__
except ImportError:
v = False
return v
#
# prompt toolkit tools
#
COLOR_CODE_SPLIT_RE = re.compile(r'(\001\033\[[\d;m]+\002)')
TERM_COLORS_REVERSED = {v: k for k, v in TERM_COLORS.items()}
COLOR_NAME_REGEX = re.compile(r'(?:(\w+)_)?(\w+)')
_PT_COLORS = {
'BLACK': '#000000',
'RED': '#800000',
'GREEN': '#008000',
'YELLOW': '#808000',
'BLUE': '#000080',
'PURPLE': '#800080',
'CYAN': '#008080',
'WHITE': '#ffffff',
'GRAY': '#008080',
'INTENSE_RED': '#ff1010',
'INTENSE_GREEN': '#00ff18',
'INTENSE_YELLOW': '#ffff00',
'INTENSE_BLUE': '#0000d2',
'INTENSE_PURPLE': '#ff00ff',
'INTENSE_CYAN': '#00ffff',
'INTENSE_GRAY': '#c0c0c0',
None: '',
}
_PT_STYLE = {
'BOLD': 'bold',
'UNDERLINE': 'underline',
}
_LAST_BG_COLOR = None
def _make_style(color_name):
""" Convert color names to pygments styles codes """
global _LAST_BG_COLOR
colors = _PT_COLORS.copy()
# Extend with custom colors
colors.update(builtins.__xonsh_env__.get('PROMPT_TOOLKIT_COLORS'))
if color_name == 'NO_COLOR':
_LAST_BG_COLOR = None
return 'noinherit'
qualifiers, name = COLOR_NAME_REGEX.match(color_name).groups()
if name not in colors:
qualifiers = name
name = None
qualifiers = qualifiers.split('_') if qualifiers else []
is_bg_color = False
style = []
for qualifier in qualifiers:
if qualifier == 'INTENSE' and name is not None:
name = 'INTENSE_' + name
elif qualifier == 'BACKGROUND':
is_bg_color = True
elif qualifier in _PT_STYLE:
style.append(_PT_STYLE[qualifier])
color = colors[name]
if is_bg_color:
_LAST_BG_COLOR = color = 'bg:' + color
elif _LAST_BG_COLOR:
style.append(_LAST_BG_COLOR)
style.append(color)
return ' '.join(style)
def format_prompt_for_prompt_toolkit(prompt):
"""Converts a prompt with color codes to a pygments style and tokens
def format_color(string, **kwargs):
"""Formats strings that may contain colors. This simply dispatches to the
shell instances method of the same name. The results of this function should
be directly usable by print_color().
"""
parts = COLOR_CODE_SPLIT_RE.split(prompt)
# ensure that parts is [colorcode, string, colorcode, string,...]
if len(parts[0]) == 0:
parts = parts[1:]
else:
parts.insert(0, '')
if len(parts) % 2 != 0:
parts.append('')
strings = parts[1::2]
token_names = [TERM_COLORS_REVERSED.get(c, 'NO_COLOR') for c in parts[::2]]
cstyles = [_make_style(c) for c in token_names]
return token_names, cstyles, strings
def format_color(string, remove_escapes=True):
"""Formats strings that contain xonsh.tools.TERM_COLORS values."""
s = string.format(**TERM_COLORS)
if remove_escapes:
s = s.replace('\001', '').replace('\002', '')
return s
return builtins.__xonsh_shell__.shell.format_color(string, **kwargs)
def print_color(string, **kwargs):
"""Print strings that contain xonsh.tools.TERM_COLORS values. By default
`sys.stdout` is used as the output stream but an alternate can be specified
by the `file` keyword argument.
"""Prints a string that may contain colors. This dispatched to the shell
method of the same name. Colors will be formatted if they have not already
been.
"""
builtins.__xonsh_shell__.shell.print_color(string, **kwargs)
@ -1073,7 +911,7 @@ def expandvars(path):
#
# File handling tools
#
#
def backup_file(fname):
"""Moves an existing file to a new name that has the current time right

View file

@ -7,16 +7,14 @@ import linecache
from functools import lru_cache
from argparse import ArgumentParser
from xonsh.tools import (DefaultNotGiven, print_color, pygments_version, normabspath,
to_bool)
from xonsh.tools import (DefaultNotGiven, print_color, pygments_version,
format_color, normabspath, to_bool, HAVE_PYGMENTS)
from xonsh import inspectors
from xonsh.environ import _replace_home as replace_home
if pygments_version():
if HAVE_PYGMENTS:
from xonsh import pyghooks
import pygments
import pygments.formatters.terminal
else:
pyghooks = None
class TracerType(object):
"""Represents a xonsh tracer object, which keeps track of all tracing
@ -28,7 +26,7 @@ class TracerType(object):
def __new__(cls, *args, **kwargs):
if cls._inst is None:
cls._inst = super(TracerType, cls).__new__(cls, *args,
cls._inst = super(TracerType, cls).__new__(cls, *args,
**kwargs)
return cls._inst
@ -79,7 +77,7 @@ class TracerType(object):
if curr != self._last:
line = linecache.getline(fname, lineno).rstrip()
s = format_line(fname, lineno, line, color=self.usecolor,
lexer=self.lexer, formatter=self.formatter).rstrip()
lexer=self.lexer, formatter=self.formatter)
print_color(s)
self._last = curr
return self.trace
@ -90,30 +88,22 @@ tracer = TracerType()
COLORLESS_LINE = '{fname}:{lineno}:{line}'
COLOR_LINE = ('{{PURPLE}}{fname}{{BLUE}}:'
'{{GREEN}}{lineno}{{BLUE}}:'
'{{NO_COLOR}}{line}')
'{{NO_COLOR}}')
RAW_COLOR_CODE_RE = re.compile(r'(\033\[[\d;]+m)')
def _escape_code_adder(m):
return '\001' + m.group(1) + '\002'
NO_SEMI_COLOR_CODE_RE = re.compile(r'(\033\[\d+m)')
def _0semi_adder(m):
return m.group(1).replace('[', '[0;')
def format_line(fname, lineno, line, color=True, lexer=None, formatter=None):
"""Formats a trace line suitable for printing."""
fname = min(fname, replace_home(fname), os.path.relpath(fname), key=len)
if not color:
return COLORLESS_LINE.format(fname=fname, lineno=lineno, line=line)
if pyghooks is not None:
lexer = lexer or pyghooks.XonshLexer()
formatter = formatter or pygments.formatters.terminal.TerminalFormatter()
line = pygments.highlight(line, lexer, formatter)
line = line.replace('{', '{{').replace('}', '}}')
line = line.replace('\033[39;49;00m', '\033[0m')
line = NO_SEMI_COLOR_CODE_RE.sub(_0semi_adder, line)
line = RAW_COLOR_CODE_RE.sub(_escape_code_adder, line)
return COLOR_LINE.format(fname=fname, lineno=lineno, line=line)
cline = COLOR_LINE.format(fname=fname, lineno=lineno)
if not HAVE_PYGMENTS:
return cline + line
# OK, so we have pygments
tokens = pyghooks.partial_color_tokenize(cline)
lexer = lexer or pyghooks.XonshLexer()
tokens += pygments.lex(line, lexer=lexer)
return tokens
#
@ -128,7 +118,7 @@ def _find_caller(args):
return fname
elif lineno == 1 and re_line.search(linecache.getline(fname, lineno)) is not None:
# There is a bug in CPython such that inspectors.getouterframes(curr, context=1)
# will actually return the 2nd line in the code_context field, even though
# will actually return the 2nd line in the code_context field, even though
# line number is itself correct. We manually fix that in this branch.
return fname
else:
@ -171,16 +161,16 @@ def _create_parser():
subp = p.add_subparsers(title='action', dest='action')
onp = subp.add_parser('on', aliases=['start', 'add'],
help='begins tracing selected files.')
onp.add_argument('files', nargs='*', default=['__file__'],
onp.add_argument('files', nargs='*', default=['__file__'],
help=('file paths to watch, use "__file__" (default) to select '
'the current file.'))
off = subp.add_parser('off', aliases=['stop', 'del', 'rm'],
help='removes selected files fom tracing.')
off.add_argument('files', nargs='*', default=['__file__'],
off.add_argument('files', nargs='*', default=['__file__'],
help=('file paths to stop watching, use "__file__" (default) to '
'select the current file.'))
col = subp.add_parser('color', help='output color management for tracer.')
col.add_argument('toggle', type=to_bool,
col.add_argument('toggle', type=to_bool,
help='true/false, y/n, etc. to toggle color usage.')
return p