Merge pull request #3878 from dyuri/1207_cutom_themes

1207 cutom themes
This commit is contained in:
Anthony Scopatz 2020-10-14 10:28:54 -05:00 committed by GitHub
commit 21e7fdd351
Failed to generate hash of commit
9 changed files with 321 additions and 4 deletions

View file

@ -56,6 +56,26 @@ To set a new theme, do
$ $XONSH_COLOR_STYLE='<theme name>'
Registering custom styles
^^^^^^^^^^^^^^^^^^^^^^^^^
If you aren't happy with the styles provided by us (and ``pygments``), you can create and register custom styles.
To do so, add something similar to your ``.xonshrc``:
.. code-block:: python
from xonsh.tools import register_custom_style
mystyle = {
"Literal.String.Single": "#ff88aa",
"Literal.String.Double": "#ff4488",
"RED": "#008800",
}
register_custom_style("mystyle", mystyle, base="monokai")
$XONSH_COLOR_STYLE="mystyle"
You can check ``xonfig colors`` for the token names. The ``base`` style will be used as a fallback for styles you don't set - pick one from ``xonfig styles`` (``default`` is used if omitted).
.. _import_local_modules:
...import python modules from a local directory?

23
news/custom_styles.rst Normal file
View file

@ -0,0 +1,23 @@
**Added:**
* Ability to register custom styles via ``xonsh.pyghooks.register_custom_style``
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* #1207 - custom color themes
**Security:**
* <news item>

View file

@ -6,6 +6,8 @@ from xonsh.ansi_colors import (
ansi_reverse_style,
ansi_color_name_to_escape_code,
ansi_color_style_names,
ansi_style_by_name,
register_custom_ansi_style,
)
@ -138,3 +140,24 @@ def test_ansi_color_escape_code_to_name(inp, exp):
def test_ansi_color_name_to_escape_code_for_all_styles(color, style):
escape_code = ansi_color_name_to_escape_code(color, style)
assert len(escape_code) > 0
@pytest.mark.parametrize(
"name, styles, refrules",
[
("test1", {}, {}),
("test2", {"Color.RED": "#ff0000"}, {"RED": "38;5;196"}),
("test3", {"BOLD_RED": "bold #ff0000"}, {"BOLD_RED": "1;38;5;196"}),
(
"test4",
{"INTENSE_RED": "italic underline bg:#ff0000 #ff0000"},
{"INTENSE_RED": "3;4;48;5;196;38;5;196"},
),
],
)
def test_register_custom_ansi_style(name, styles, refrules):
register_custom_ansi_style(name, styles)
style = ansi_style_by_name(name)
assert style is not None
for key, value in refrules.items():
assert style[key] == value

View file

@ -10,10 +10,13 @@ from xonsh.platform import ON_WINDOWS
from xonsh.pyghooks import (
XonshStyle,
Color,
Token,
color_name_to_pygments_code,
code_by_name,
color_file,
file_color_tokens,
get_style_by_name,
register_custom_pygments_style,
)
from xonsh.environ import LsColors
@ -337,3 +340,37 @@ def test_colorize_file_ca(xonsh_builtins_LS_COLORS, monkeypatch):
color_token, color_key = color_file(file_path, os.lstat(file_path))
assert color_key == "ca"
@pytest.mark.parametrize(
"name, styles, refrules",
[
("test1", {}, {}), # empty styles
(
"test2",
{Token.Literal.String.Single: "#ff0000"},
{Token.Literal.String.Single: "#ff0000"},
), # Token
(
"test3",
{"Token.Literal.String.Single": "#ff0000"},
{Token.Literal.String.Single: "#ff0000"},
), # str key
(
"test4",
{"Literal.String.Single": "#ff0000"},
{Token.Literal.String.Single: "#ff0000"},
), # short str key
],
)
def test_register_custom_pygments_style(name, styles, refrules):
register_custom_pygments_style(name, styles)
style = get_style_by_name(name)
# registration succeeded
assert style is not None
# check rules
for rule, color in refrules.items():
assert rule in style.styles
assert style.styles[rule] == color

View file

@ -10,7 +10,10 @@ import warnings
import pytest
from xonsh import __version__
from xonsh.platform import ON_WINDOWS
from xonsh.platform import (
ON_WINDOWS,
HAS_PYGMENTS,
)
from xonsh.lexer import Lexer
from xonsh.tools import (
@ -80,6 +83,7 @@ from xonsh.tools import (
balanced_parens,
iglobpath,
all_permutations,
register_custom_style,
simple_random_choice,
)
from xonsh.environ import Env
@ -1788,3 +1792,30 @@ def test_all_permutations():
"ABC",
}
assert obs == exp
@pytest.mark.parametrize(
"name, styles, refrules",
[
("test1", {}, {}), # empty styles
(
"test2",
{"Token.Literal.String.Single": "#ff0000"},
{"Token.Literal.String.Single": "#ff0000"},
), # str key
(
"test3",
{"Literal.String.Single": "#ff0000"},
{"Token.Literal.String.Single": "#ff0000"},
), # short str key
("test4", {"RED": "#ff0000"}, {"Token.Color.RED": "#ff0000"},), # color
],
)
def test_register_custom_style(name, styles, refrules):
style = register_custom_style(name, styles)
if HAS_PYGMENTS:
assert style is not None
for rule, color in style.styles.items():
if str(rule) in refrules:
assert refrules[str(rule)] == color

View file

@ -266,7 +266,7 @@ _ANSI_COLOR_ESCAPE_CODE_TO_NAME_CACHE: tp.Dict[str, tp.Tuple[str, ...]] = {}
def ansi_color_escape_code_to_name(escape_code, style, reversed_style=None):
"""Converts an ASNI color code escape sequence to a tuple of color names
"""Converts an ANSI color code escape sequence to a tuple of color names
in the provided style ('default' should almost be the style). For example,
'0' becomes ('RESET',) and '32;41' becomes ('GREEN', 'BACKGROUND_RED').
The style keyword may either be a string, in which the style is looked up,
@ -1076,6 +1076,57 @@ def make_ansi_style(palette):
return style
def _pygments_to_ansi_style(style):
"""Tries to convert the given pygments style to ANSI style.
Parameter
---------
style : pygments style value
Returns
-------
ANSI style
"""
ansi_style_list = []
parts = style.split(" ")
for part in parts:
if part == "bold":
ansi_style_list.append("1")
elif part == "italic":
ansi_style_list.append("3")
elif part == "underline":
ansi_style_list.append("4")
elif part[:3] == "bg:":
ansi_style_list.append("48;5;" + rgb2short(part[3:])[0])
else:
ansi_style_list.append("38;5;" + rgb2short(part)[0])
return ";".join(ansi_style_list)
def register_custom_ansi_style(name, styles, base="default"):
"""Register custom ANSI style.
Parameters
----------
name : str
Style name.
styles : dict
Token (or str) -> style mapping.
base : str, optional
Base style to use as default.
"""
base_style = ANSI_STYLES[base].copy()
for token, style in styles.items():
token = str(token) # convert pygments token to str
parts = token.split(".")
if len(parts) == 1 or parts[-2] == "Color":
base_style[parts[-1]] = _pygments_to_ansi_style(style)
ANSI_STYLES[name] = base_style
def ansi_style_by_name(name):
"""Gets or makes an ANSI color style by name. If the styles does not
exist, it will look for a style using the pygments name.

View file

@ -61,7 +61,7 @@ from xonsh.platform import (
pygments_version_info,
)
from xonsh.pygments_cache import get_style_by_name
from xonsh.pygments_cache import get_style_by_name, add_custom_style
from xonsh.events import events
@ -234,7 +234,8 @@ def color_token_by_name(xc: tuple, styles=None) -> _TokenType:
tokName += "__" + xc[1]
token = getattr(Color, norm_name(tokName))
styles[token] = pc
if token not in styles:
styles[token] = pc
return token
@ -437,6 +438,82 @@ def xonsh_style_proxy(styler):
return XonshStyleProxy
def _get_token_by_name(name):
"""Get pygments token object by its string representation."""
token = Token
parts = name.split(".")
if len(parts) == 1:
parts = ["Color"] + parts
if parts[0] == "Token":
parts = parts[1:]
while len(parts):
token = getattr(token, parts[0])
parts = parts[1:]
return token
def register_custom_pygments_style(
name, styles, highlight_color=None, background_color=None, base="default"
):
"""Register custom style.
Parameters
----------
name : str
Style name.
styles : dict
Token -> style mapping.
highlight_color : str
Hightlight color.
background_color : str
Background color.
base : str, optional
Base style to use as default.
Returns
-------
style : The ``pygments.Style`` subclass created
"""
base_style = get_style_by_name(base)
custom_styles = base_style.styles.copy()
for token, value in styles.items():
if isinstance(token, str):
token = _get_token_by_name(token)
custom_styles[token] = value
style = type(
f"Custom{name}Style",
(Style,),
{
"styles": custom_styles,
"highlight_color": highlight_color
if highlight_color is not None
else base_style.highlight_color,
"background_color": background_color
if background_color is not None
else base_style.background_color,
},
)
add_custom_style(name, style)
cmap = pygments_style_by_name(base).copy()
# replace colors in color map if found in styles
for token in cmap.keys():
if token in custom_styles:
cmap[token] = custom_styles[token]
STYLES[name] = cmap
return style
PTK_STYLE = LazyObject(
lambda: {
Token.Menu.Completions: "bg:ansigray ansiblack",

View file

@ -20,6 +20,7 @@ import importlib
# Global storage variables
__version__ = "0.1.1"
CACHE = None
CUSTOM_STYLES = {}
DEBUG = False
@ -266,6 +267,19 @@ def cache_filename():
)
def add_custom_style(name, style):
"""Register custom style to be able to retrieve it by ``get_style_by_name``.
Parameters
----------
name : str
Style name.
style : pygments.Style
Custom style to add.
"""
CUSTOM_STYLES[name] = style
def load(filename):
"""Loads the cache from a filename."""
global CACHE
@ -407,6 +421,8 @@ def get_style_by_name(name):
modname, clsname = names[name]
mod = importlib.import_module(modname)
style = getattr(mod, clsname)
elif name in CUSTOM_STYLES:
style = CUSTOM_STYLES[name]
else:
# couldn't find style in cache, fallback to the hard way
import inspect
@ -427,6 +443,7 @@ def get_all_styles():
if CACHE is None:
load_or_build()
yield from CACHE["styles"]["names"]
yield from CUSTOM_STYLES
def get_filter_by_name(filtername, **options):

View file

@ -1860,6 +1860,44 @@ def color_style():
return builtins.__xonsh__.shell.shell.color_style()
def register_custom_style(
name, styles, highlight_color=None, background_color=None, base="default"
):
"""Register custom style.
Parameters
----------
name : str
Style name.
styles : dict
Token -> style mapping.
highlight_color : str
Hightlight color.
background_color : str
Background color.
base : str, optional
Base style to use as default.
Returns
-------
style : The style object created, None if not succeeded
"""
style = None
if pygments_version_info():
from xonsh.pyghooks import register_custom_pygments_style
style = register_custom_pygments_style(
name, styles, highlight_color, background_color, base
)
# register ANSI colors
from xonsh.ansi_colors import register_custom_ansi_style
register_custom_ansi_style(name, styles, base)
return style
def _token_attr_from_stylemap(stylemap):
"""yields tokens attr, and index from a stylemap """
import prompt_toolkit as ptk