mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
Merge pull request #3878 from dyuri/1207_cutom_themes
1207 cutom themes
This commit is contained in:
commit
21e7fdd351
9 changed files with 321 additions and 4 deletions
|
@ -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
23
news/custom_styles.rst
Normal 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>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue