Skip to content

ANSI dim text styling #1999

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
9 changes: 7 additions & 2 deletions src/prompt_toolkit/formatted_text/ansi.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(self, value: str) -> None:
self._color: str | None = None
self._bgcolor: str | None = None
self._bold = False
self._dim = False
self._underline = False
self._strike = False
self._italic = False
Expand Down Expand Up @@ -153,8 +154,8 @@ def _select_graphic_rendition(self, attrs: list[int]) -> None:
self._bgcolor = _bg_colors[attr]
elif attr == 1:
self._bold = True
# elif attr == 2:
# self._faint = True
elif attr == 2:
self._dim = True
elif attr == 3:
self._italic = True
elif attr == 4:
Expand All @@ -171,6 +172,7 @@ def _select_graphic_rendition(self, attrs: list[int]) -> None:
self._strike = True
elif attr == 22:
self._bold = False # Normal intensity
self._dim = False
elif attr == 23:
self._italic = False
elif attr == 24:
Expand All @@ -188,6 +190,7 @@ def _select_graphic_rendition(self, attrs: list[int]) -> None:
self._color = None
self._bgcolor = None
self._bold = False
self._dim = False
self._underline = False
self._strike = False
self._italic = False
Expand Down Expand Up @@ -232,6 +235,8 @@ def _create_style_string(self) -> str:
result.append("bg:" + self._bgcolor)
if self._bold:
result.append("bold")
if self._dim:
result.append("dim")
if self._underline:
result.append("underline")
if self._strike:
Expand Down
5 changes: 4 additions & 1 deletion src/prompt_toolkit/output/vt100.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def __missing__(self, value: tuple[int, int, int]) -> int:
class _EscapeCodeCache(Dict[Attrs, str]):
"""
Cache for VT100 escape codes. It maps
(fgcolor, bgcolor, bold, underline, strike, reverse) tuples to VT100
(fgcolor, bgcolor, bold, underline, strike, italic, blink, reverse, hidden, dim) tuples to VT100
escape sequences.

:param true_color: When True, use 24bit colors instead of 256 colors.
Expand All @@ -277,13 +277,16 @@ def __missing__(self, attrs: Attrs) -> str:
blink,
reverse,
hidden,
dim,
) = attrs
parts: list[str] = []

parts.extend(self._colors_to_code(fgcolor or "", bgcolor or ""))

if bold:
parts.append("1")
if dim:
parts.append("2")
if italic:
parts.append("3")
if blink:
Expand Down
1 change: 1 addition & 0 deletions src/prompt_toolkit/output/win32.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
blink,
reverse,
hidden,
dim,
) = attrs
self._hidden = bool(hidden)

Expand Down
3 changes: 3 additions & 0 deletions src/prompt_toolkit/styles/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Attrs(NamedTuple):
blink: bool | None
reverse: bool | None
hidden: bool | None
dim: bool | None


"""
Expand All @@ -41,6 +42,7 @@ class Attrs(NamedTuple):
:param blink: Boolean
:param reverse: Boolean
:param hidden: Boolean
:param dim: Boolean
"""

#: The default `Attrs`.
Expand All @@ -54,6 +56,7 @@ class Attrs(NamedTuple):
blink=False,
reverse=False,
hidden=False,
dim=False,
)


Expand Down
6 changes: 6 additions & 0 deletions src/prompt_toolkit/styles/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def parse_color(text: str) -> str:
blink=None,
reverse=None,
hidden=None,
dim=None,
)


Expand Down Expand Up @@ -151,6 +152,10 @@ def _parse_style_str(style_str: str) -> Attrs:
attrs = attrs._replace(hidden=True)
elif part == "nohidden":
attrs = attrs._replace(hidden=False)
elif part == "dim":
attrs = attrs._replace(dim=True)
elif part == "nodim":
attrs = attrs._replace(dim=False)

# Pygments properties that we ignore.
elif part in ("roman", "sans", "mono"):
Expand Down Expand Up @@ -345,6 +350,7 @@ def _or(*values: _T) -> _T:
blink=_or(False, *[a.blink for a in list_of_attrs]),
reverse=_or(False, *[a.reverse for a in list_of_attrs]),
hidden=_or(False, *[a.hidden for a in list_of_attrs]),
dim=_or(False, *[a.dim for a in list_of_attrs]),
)


Expand Down
40 changes: 40 additions & 0 deletions tests/test_formatted_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,46 @@ def test_ansi_formatting():
assert isinstance(to_formatted_text(value), FormattedText)


def test_ansi_dim():
# Test dim formatting
value = ANSI("\x1b[2mhello\x1b[0m")

assert to_formatted_text(value) == [
("dim", "h"),
("dim", "e"),
("dim", "l"),
("dim", "l"),
("dim", "o"),
]

# Test dim with other attributes
value = ANSI("\x1b[1;2;31mhello\x1b[0m")

assert to_formatted_text(value) == [
("ansired bold dim", "h"),
("ansired bold dim", "e"),
("ansired bold dim", "l"),
("ansired bold dim", "l"),
("ansired bold dim", "o"),
]

# Test dim reset with code 22
value = ANSI("\x1b[1;2mhello\x1b[22mworld\x1b[0m")

assert to_formatted_text(value) == [
("bold dim", "h"),
("bold dim", "e"),
("bold dim", "l"),
("bold dim", "l"),
("bold dim", "o"),
("", "w"),
("", "o"),
("", "r"),
("", "l"),
("", "d"),
]


def test_ansi_256_color():
assert to_formatted_text(ANSI("\x1b[38;5;124mtest")) == [
("#af0000", "t"),
Expand Down
19 changes: 19 additions & 0 deletions tests/test_print_formatted_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,22 @@ def test_html_with_style():
f.data
== "\x1b[0m\x1b[?7h\x1b[0;32mhello\x1b[0m \x1b[0;1mworld\x1b[0m\r\n\x1b[0m"
)


@pytest.mark.skipif(is_windows(), reason="Doesn't run on Windows yet.")
def test_print_formatted_text_with_dim():
"""
Test that dim formatting works correctly.
"""
f = _Capture()
style = Style.from_dict(
{
"dimtext": "dim",
}
)
tokens = FormattedText([("class:dimtext", "dim text")])

pt_print(tokens, style=style, file=f, color_depth=ColorDepth.DEFAULT)

# Check that the ANSI dim escape code (ESC[2m) is in the output
assert "\x1b[0;2m" in f.data or "\x1b[2m" in f.data
16 changes: 16 additions & 0 deletions tests/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def test_style_from_dict():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("class:a") == expected

Expand All @@ -36,6 +37,7 @@ def test_style_from_dict():
blink=True,
reverse=True,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("class:b") == expected

Expand All @@ -50,6 +52,7 @@ def test_style_from_dict():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("#ff0000") == expected

Expand All @@ -64,6 +67,7 @@ def test_style_from_dict():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("class:a #00ff00") == expected

Expand All @@ -77,6 +81,7 @@ def test_style_from_dict():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("#00ff00 class:a") == expected

Expand All @@ -101,6 +106,7 @@ def test_class_combinations_1():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("class:a class:b") == expected
assert style.get_attrs_for_style_str("class:a,b") == expected
Expand Down Expand Up @@ -131,6 +137,7 @@ def test_class_combinations_2():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("class:a class:b") == expected
assert style.get_attrs_for_style_str("class:a,b") == expected
Expand All @@ -147,6 +154,7 @@ def test_class_combinations_2():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("class:b class:a") == expected
assert style.get_attrs_for_style_str("class:b,a") == expected
Expand All @@ -173,6 +181,7 @@ def test_substyles():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("class:a") == expected

Expand All @@ -186,6 +195,7 @@ def test_substyles():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("class:a.b") == expected
assert style.get_attrs_for_style_str("class:a.b.c") == expected
Expand All @@ -201,6 +211,7 @@ def test_substyles():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("class:b") == expected
assert style.get_attrs_for_style_str("class:b.a") == expected
Expand All @@ -215,6 +226,7 @@ def test_substyles():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
assert style.get_attrs_for_style_str("class:b.c") == expected
assert style.get_attrs_for_style_str("class:b.c.d") == expected
Expand All @@ -234,6 +246,7 @@ def test_swap_light_and_dark_style_transformation():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
after = Attrs(
color="ffbbbb",
Expand All @@ -245,6 +258,7 @@ def test_swap_light_and_dark_style_transformation():
blink=False,
reverse=False,
hidden=False,
dim=False,
)

assert transformation.transform_attrs(before) == after
Expand All @@ -260,6 +274,7 @@ def test_swap_light_and_dark_style_transformation():
blink=False,
reverse=False,
hidden=False,
dim=False,
)
after = Attrs(
color="ansibrightred",
Expand All @@ -271,6 +286,7 @@ def test_swap_light_and_dark_style_transformation():
blink=False,
reverse=False,
hidden=False,
dim=False,
)

assert transformation.transform_attrs(before) == after
1 change: 1 addition & 0 deletions tests/test_style_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def default_attrs():
blink=False,
reverse=False,
hidden=False,
dim=False,
)


Expand Down