Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Unreleased

### Features

- Add Solarized syntax highlighting styles.

### Bug Fixes

- Expand `~` in configured log file paths before opening the log.
Expand Down
175 changes: 157 additions & 18 deletions litecli/clistyle.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
from __future__ import annotations

import logging
from typing import cast

import pygments.styles
from prompt_toolkit.styles import Style, merge_styles
from prompt_toolkit.styles.pygments import style_from_pygments_cls
from prompt_toolkit.styles.style import _MergedStyle
from pygments.style import Style as PygmentsStyle
from pygments.token import Token, _TokenType, string_to_tokentype
from pygments.token import (
Comment,
Error,
Generic,
Keyword,
Name,
Number,
Operator,
String,
Token,
_TokenType,
string_to_tokentype,
)
from pygments.util import ClassNotFound

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -46,7 +57,135 @@
PROMPT_STYLE_TO_TOKEN: dict[str, _TokenType] = {v: k for k, v in TOKEN_TO_PROMPT_STYLE.items()}


def parse_pygments_style(token_name: str, style_object: PygmentsStyle | dict, style_dict: dict[str, str]) -> tuple[_TokenType, str]:
def _make_solarized_style(colors: dict[str, str]) -> dict[_TokenType, str]:
return {
Token: colors["base0"],
Comment: "italic " + colors["base01"],
Comment.Hashbang: colors["base01"],
Comment.Multiline: colors["base01"],
Comment.Preproc: "noitalic " + colors["magenta"],
Comment.PreprocFile: "noitalic " + colors["base01"],
Keyword: colors["green"],
Keyword.Constant: colors["cyan"],
Keyword.Declaration: colors["cyan"],
Keyword.Namespace: colors["orange"],
Keyword.Type: colors["yellow"],
Operator: colors["base01"],
Operator.Word: colors["green"],
Name.Builtin: colors["blue"],
Name.Builtin.Pseudo: colors["blue"],
Name.Class: colors["blue"],
Name.Constant: colors["blue"],
Name.Decorator: colors["blue"],
Name.Entity: colors["blue"],
Name.Exception: colors["blue"],
Name.Function: colors["blue"],
Name.Function.Magic: colors["blue"],
Name.Label: colors["blue"],
Name.Namespace: colors["blue"],
Name.Tag: colors["blue"],
Name.Variable: colors["blue"],
Name.Variable.Global: colors["blue"],
Name.Variable.Magic: colors["blue"],
String: colors["cyan"],
String.Doc: colors["base01"],
String.Regex: colors["orange"],
Number: colors["cyan"],
Generic: colors["base0"],
Generic.Deleted: colors["red"],
Generic.Emph: "italic",
Generic.Error: colors["red"],
Generic.Heading: "bold",
Generic.Subheading: "underline",
Generic.Inserted: colors["green"],
Generic.Output: colors["base0"],
Generic.Prompt: "bold " + colors["blue"],
Generic.Strong: "bold",
Generic.EmphStrong: "bold italic",
Generic.Traceback: colors["blue"],
Error: "bg:" + colors["red"],
}


SOLARIZED_DARK_COLORS = {
"base03": "#002b36",
"base02": "#073642",
"base01": "#586e75",
"base00": "#657b83",
"base0": "#839496",
"base1": "#93a1a1",
"base2": "#eee8d5",
"base3": "#fdf6e3",
"yellow": "#b58900",
"orange": "#cb4b16",
"red": "#dc322f",
"magenta": "#d33682",
"violet": "#6c71c4",
"blue": "#268bd2",
"cyan": "#2aa198",
"green": "#859900",
}

SOLARIZED_LIGHT_COLORS = {
"base3": "#002b36",
"base2": "#073642",
"base1": "#586e75",
"base0": "#657b83",
"base00": "#839496",
"base01": "#93a1a1",
"base02": "#eee8d5",
"base03": "#fdf6e3",
"yellow": "#b58900",
"orange": "#cb4b16",
"red": "#dc322f",
"magenta": "#d33682",
"violet": "#6c71c4",
"blue": "#268bd2",
"cyan": "#2aa198",
"green": "#859900",
}


class SolarizedDarkStyle(PygmentsStyle):
name = "solarized-dark"
background_color = SOLARIZED_DARK_COLORS["base03"]
highlight_color = SOLARIZED_DARK_COLORS["base02"]
line_number_color = SOLARIZED_DARK_COLORS["base01"]
line_number_background_color = SOLARIZED_DARK_COLORS["base02"]
styles = _make_solarized_style(SOLARIZED_DARK_COLORS)


class SolarizedLightStyle(PygmentsStyle):
name = "solarized-light"
background_color = SOLARIZED_LIGHT_COLORS["base03"]
highlight_color = SOLARIZED_LIGHT_COLORS["base02"]
line_number_color = SOLARIZED_LIGHT_COLORS["base01"]
line_number_background_color = SOLARIZED_LIGHT_COLORS["base02"]
styles = _make_solarized_style(SOLARIZED_LIGHT_COLORS)


CUSTOM_STYLES: dict[str, type[PygmentsStyle]] = {
"solarized": SolarizedDarkStyle,
"solarized-dark": SolarizedDarkStyle,
"solarized-light": SolarizedLightStyle,
}


def get_style(name: str) -> type[PygmentsStyle]:
if name in CUSTOM_STYLES:
return CUSTOM_STYLES[name]

try:
return pygments.styles.get_style_by_name(name)
except ClassNotFound:
return pygments.styles.get_style_by_name("native")


def parse_pygments_style(
token_name: str,
style_object: type[PygmentsStyle] | dict[_TokenType, str],
style_dict: dict[str, str],
) -> tuple[_TokenType, str]:
"""Parse token type and style string.

:param token_name: str name of Pygments token. Example: "Token.String"
Expand All @@ -55,18 +194,16 @@ def parse_pygments_style(token_name: str, style_object: PygmentsStyle | dict, st

"""
token_type = string_to_tokentype(token_name)
if isinstance(style_object, PygmentsStyle):
other_token_type = string_to_tokentype(style_dict[token_name])
style_value = style_dict[token_name]
if isinstance(style_object, type) and issubclass(style_object, PygmentsStyle) and style_value.startswith("Token."):
other_token_type = string_to_tokentype(style_value)
return token_type, style_object.styles[other_token_type]
else:
return token_type, style_dict[token_name]
return token_type, style_value


def style_factory(name: str, cli_style: dict[str, str]) -> _MergedStyle:
try:
style = pygments.styles.get_style_by_name(name)
except ClassNotFound:
style = pygments.styles.get_style_by_name("native")
style = get_style(name)

prompt_styles: list[tuple[str, str]] = []
# prompt-toolkit used pygments tokens for styling before, switched to style
Expand All @@ -90,11 +227,9 @@ def style_factory(name: str, cli_style: dict[str, str]) -> _MergedStyle:
return merge_styles([style_from_pygments_cls(style), override_style, Style(prompt_styles)])


def style_factory_output(name: str, cli_style: dict[str, str]) -> PygmentsStyle:
try:
style = pygments.styles.get_style_by_name(name).styles
except ClassNotFound:
style = pygments.styles.get_style_by_name("native").styles
def style_factory_output(name: str, cli_style: dict[str, str]) -> type[PygmentsStyle]:
style_cls = get_style(name)
style = dict(style_cls.styles)

for token in cli_style:
if token.startswith("Token."):
Expand All @@ -109,7 +244,11 @@ def style_factory_output(name: str, cli_style: dict[str, str]) -> PygmentsStyle:

class OutputStyle(PygmentsStyle):
default_style = ""
styles = style

# mypy does not complain but ty complains: error[invalid-return-type]: Return type does not match returned value. Hence added cast.
return cast(OutputStyle, PygmentsStyle)
OutputStyle.background_color = style_cls.background_color
OutputStyle.highlight_color = style_cls.highlight_color
OutputStyle.line_number_color = style_cls.line_number_color
OutputStyle.line_number_background_color = style_cls.line_number_background_color
OutputStyle.styles = style

return OutputStyle
2 changes: 1 addition & 1 deletion litecli/liteclirc
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ table_format = ascii
# Syntax coloring style. Possible values (many support the "-dark" suffix):
# manni, igor, xcode, vim, autumn, vs, rrt, native, perldoc, borland, tango, emacs,
# friendly, monokai, paraiso, colorful, murphy, bw, pastie, paraiso, trac, default,
# fruity.
# fruity, solarized, solarized-dark, solarized-light.
# See the LiteCLI README for syntax examples
syntax_style = default

Expand Down
2 changes: 1 addition & 1 deletion tests/liteclirc
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ table_format = ascii
# Syntax coloring style. Possible values (many support the "-dark" suffix):
# manni, igor, xcode, vim, autumn, vs, rrt, native, perldoc, borland, tango, emacs,
# friendly, monokai, paraiso, colorful, murphy, bw, pastie, paraiso, trac, default,
# fruity.
# fruity, solarized, solarized-dark, solarized-light.
# Screenshots at http://mycli.net/syntax
syntax_style = default

Expand Down
20 changes: 18 additions & 2 deletions tests/test_clistyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

import pytest
from pygments.style import Style
from pygments.token import Token
from pygments.token import Keyword, String, Token

from litecli.clistyle import style_factory
from litecli.clistyle import SolarizedDarkStyle, SolarizedLightStyle, style_factory, style_factory_output


@pytest.mark.skip(reason="incompatible with new prompt toolkit")
Expand All @@ -27,3 +27,19 @@ def test_style_factory_unknown_name():
style = style_factory("foobar", {})

assert isinstance(style, Style)


@pytest.mark.parametrize(
("name", "style", "background"),
[
("solarized", SolarizedDarkStyle, "#002b36"),
("solarized-dark", SolarizedDarkStyle, "#002b36"),
("solarized-light", SolarizedLightStyle, "#fdf6e3"),
],
)
def test_style_factory_output_solarized(name, style, background):
output_style = style_factory_output(name, {})

assert output_style.styles[Keyword] == style.styles[Keyword]
assert output_style.styles[String] == style.styles[String]
assert output_style.background_color == background
Loading