Skip to main content

src/etui@style.erl

-module(etui@style).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/style.gleam").
-export([none/0, bold/0, dim/0, italic/0, underline/0, blink/0, reverse/0, strikethrough/0, add/2, remove/2, has/2, is_none/1, modifier_equal/2, default_style/0, with_fg/2, with_bg/2, with_modifier/2, bold_style/0, reversed/0, italic_style/0, dim_style/0, underline_style/0, add_modifier/2, remove_modifier/2, color_from_hex/1, patch/2, ansi_fg/1, ansi_bg/1, ansi_modifier/1, ansi_reset/0]).
-export_type([color/0, modifier/0, style/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

-type color() :: default |
    {indexed, integer()} |
    {rgb, integer(), integer(), integer()}.

-opaque modifier() :: {modifier, integer()}.

-type style() :: {style, color(), color(), modifier()}.

-file("src/etui/style.gleam", 27).
?DOC(" No modifiers active.\n").
-spec none() -> modifier().
none() ->
    {modifier, 0}.

-file("src/etui/style.gleam", 32).
?DOC(" Bold / increased intensity.\n").
-spec bold() -> modifier().
bold() ->
    {modifier, 1}.

-file("src/etui/style.gleam", 37).
?DOC(" Dim / decreased intensity.\n").
-spec dim() -> modifier().
dim() ->
    {modifier, 2}.

-file("src/etui/style.gleam", 42).
?DOC(" Italic text.\n").
-spec italic() -> modifier().
italic() ->
    {modifier, 4}.

-file("src/etui/style.gleam", 47).
?DOC(" Underline.\n").
-spec underline() -> modifier().
underline() ->
    {modifier, 8}.

-file("src/etui/style.gleam", 52).
?DOC(" Blinking text (terminal support varies).\n").
-spec blink() -> modifier().
blink() ->
    {modifier, 16}.

-file("src/etui/style.gleam", 57).
?DOC(" Swap foreground and background colors.\n").
-spec reverse() -> modifier().
reverse() ->
    {modifier, 32}.

-file("src/etui/style.gleam", 62).
?DOC(" Strikethrough.\n").
-spec strikethrough() -> modifier().
strikethrough() ->
    {modifier, 64}.

-file("src/etui/style.gleam", 70).
?DOC(" Combine two modifiers (bitwise OR).\n").
-spec add(modifier(), modifier()) -> modifier().
add(A, B) ->
    {modifier, erlang:'bor'(erlang:element(2, A), erlang:element(2, B))}.

-file("src/etui/style.gleam", 75).
?DOC(" Remove modifier bits from `a` that are set in `b`.\n").
-spec remove(modifier(), modifier()) -> modifier().
remove(A, B) ->
    {modifier,
        erlang:'band'(erlang:element(2, A), erlang:'bnot'(erlang:element(2, B)))}.

-file("src/etui/style.gleam", 80).
?DOC(" Check if `flag` bits are set in `m`.\n").
-spec has(modifier(), modifier()) -> boolean().
has(M, Flag) ->
    erlang:'band'(erlang:element(2, M), erlang:element(2, Flag)) /= 0.

-file("src/etui/style.gleam", 85).
?DOC(" True when no modifier bits are set.\n").
-spec is_none(modifier()) -> boolean().
is_none(M) ->
    erlang:element(2, M) =:= 0.

-file("src/etui/style.gleam", 90).
?DOC(" Structural equality for modifiers.\n").
-spec modifier_equal(modifier(), modifier()) -> boolean().
modifier_equal(A, B) ->
    erlang:element(2, A) =:= erlang:element(2, B).

-file("src/etui/style.gleam", 103).
?DOC(" Default style: terminal colors, no modifiers.\n").
-spec default_style() -> style().
default_style() ->
    {style, default, default, none()}.

-file("src/etui/style.gleam", 108).
?DOC(" Set foreground color on a style.\n").
-spec with_fg(style(), color()) -> style().
with_fg(S, Fg) ->
    {style, Fg, erlang:element(3, S), erlang:element(4, S)}.

-file("src/etui/style.gleam", 113).
?DOC(" Set background color on a style.\n").
-spec with_bg(style(), color()) -> style().
with_bg(S, Bg) ->
    {style, erlang:element(2, S), Bg, erlang:element(4, S)}.

-file("src/etui/style.gleam", 118).
?DOC(" Set modifier on a style.\n").
-spec with_modifier(style(), modifier()) -> style().
with_modifier(S, M) ->
    {style, erlang:element(2, S), erlang:element(3, S), M}.

-file("src/etui/style.gleam", 123).
?DOC(" Default colors with bold modifier.\n").
-spec bold_style() -> style().
bold_style() ->
    {style, default, default, bold()}.

-file("src/etui/style.gleam", 128).
?DOC(" Default colors with reverse modifier (swap fg/bg).\n").
-spec reversed() -> style().
reversed() ->
    {style, default, default, reverse()}.

-file("src/etui/style.gleam", 133).
?DOC(" Default colors with italic modifier.\n").
-spec italic_style() -> style().
italic_style() ->
    {style, default, default, italic()}.

-file("src/etui/style.gleam", 138).
?DOC(" Default colors with dim modifier.\n").
-spec dim_style() -> style().
dim_style() ->
    {style, default, default, dim()}.

-file("src/etui/style.gleam", 143).
?DOC(" Default colors with underline modifier.\n").
-spec underline_style() -> style().
underline_style() ->
    {style, default, default, underline()}.

-file("src/etui/style.gleam", 148).
?DOC(" Add a modifier to a `Style` (bitwise OR).\n").
-spec add_modifier(style(), modifier()) -> style().
add_modifier(S, M) ->
    {style,
        erlang:element(2, S),
        erlang:element(3, S),
        add(erlang:element(4, S), M)}.

-file("src/etui/style.gleam", 153).
?DOC(" Remove modifier bits from a `Style`.\n").
-spec remove_modifier(style(), modifier()) -> style().
remove_modifier(S, M) ->
    {style,
        erlang:element(2, S),
        erlang:element(3, S),
        remove(erlang:element(4, S), M)}.

-file("src/etui/style.gleam", 192).
-spec hex_digit(binary()) -> {ok, integer()} | {error, nil}.
hex_digit(C) ->
    case C of
        <<"0"/utf8>> ->
            {ok, 0};

        <<"1"/utf8>> ->
            {ok, 1};

        <<"2"/utf8>> ->
            {ok, 2};

        <<"3"/utf8>> ->
            {ok, 3};

        <<"4"/utf8>> ->
            {ok, 4};

        <<"5"/utf8>> ->
            {ok, 5};

        <<"6"/utf8>> ->
            {ok, 6};

        <<"7"/utf8>> ->
            {ok, 7};

        <<"8"/utf8>> ->
            {ok, 8};

        <<"9"/utf8>> ->
            {ok, 9};

        <<"a"/utf8>> ->
            {ok, 10};

        <<"A"/utf8>> ->
            {ok, 10};

        <<"b"/utf8>> ->
            {ok, 11};

        <<"B"/utf8>> ->
            {ok, 11};

        <<"c"/utf8>> ->
            {ok, 12};

        <<"C"/utf8>> ->
            {ok, 12};

        <<"d"/utf8>> ->
            {ok, 13};

        <<"D"/utf8>> ->
            {ok, 13};

        <<"e"/utf8>> ->
            {ok, 14};

        <<"E"/utf8>> ->
            {ok, 14};

        <<"f"/utf8>> ->
            {ok, 15};

        <<"F"/utf8>> ->
            {ok, 15};

        _ ->
            {error, nil}
    end.

-file("src/etui/style.gleam", 185).
-spec hex_pair(binary(), binary()) -> {ok, integer()} | {error, nil}.
hex_pair(Hi, Lo) ->
    case {hex_digit(Hi), hex_digit(Lo)} of
        {{ok, H}, {ok, L}} ->
            {ok, (H * 16) + L};

        {_, _} ->
            {error, nil}
    end.

-file("src/etui/style.gleam", 164).
?DOC(
    " Parse an RGB color from a hex string (`\"#RRGGBB\"` or `\"RRGGBB\"`).\n"
    " Returns `Error(Nil)` for malformed input.\n"
    "\n"
    " ```gleam\n"
    " style.color_from_hex(\"#1e1e2e\")  // Ok(Rgb(30, 30, 46))\n"
    " style.color_from_hex(\"ff5555\")   // Ok(Rgb(255, 85, 85))\n"
    " ```\n"
).
-spec color_from_hex(binary()) -> {ok, color()} | {error, nil}.
color_from_hex(Hex) ->
    S = case gleam_stdlib:string_starts_with(Hex, <<"#"/utf8>>) of
        true ->
            gleam@string:drop_start(Hex, 1);

        false ->
            Hex
    end,
    case string:length(S) =:= 6 of
        false ->
            {error, nil};

        true ->
            Chars = gleam@string:to_graphemes(S),
            case Chars of
                [R1, R2, G1, G2, B1, B2] ->
                    case {hex_pair(R1, R2), hex_pair(G1, G2), hex_pair(B1, B2)} of
                        {{ok, R}, {ok, G}, {ok, B}} ->
                            {ok, {rgb, R, G, B}};

                        {_, _, _} ->
                            {error, nil}
                    end;

                _ ->
                    {error, nil}
            end
    end.

-file("src/etui/style.gleam", 217).
?DOC(
    " Apply `over` on top of `base`. Default fg/bg fall back to `base`.\n"
    " Modifier: if `over` has any bits set, they are OR'd into `base`;\n"
    " `none()` in `over` means \"no modifier override\" (keep base).\n"
).
-spec patch(style(), style()) -> style().
patch(Base, Over) ->
    Fg = case erlang:element(2, Over) of
        default ->
            erlang:element(2, Base);

        C ->
            C
    end,
    Bg = case erlang:element(3, Over) of
        default ->
            erlang:element(3, Base);

        C@1 ->
            C@1
    end,
    Modifier = case is_none(erlang:element(4, Over)) of
        true ->
            erlang:element(4, Base);

        false ->
            add(erlang:element(4, Base), erlang:element(4, Over))
    end,
    {style, Fg, Bg, Modifier}.

-file("src/etui/style.gleam", 237).
?DOC(" Foreground color escape sequence.\n").
-spec ansi_fg(color()) -> binary().
ansi_fg(Color) ->
    case Color of
        default ->
            <<""/utf8>>;

        {indexed, 0} ->
            <<"\x{001B}[30m"/utf8>>;

        {indexed, 1} ->
            <<"\x{001B}[31m"/utf8>>;

        {indexed, 2} ->
            <<"\x{001B}[32m"/utf8>>;

        {indexed, 3} ->
            <<"\x{001B}[33m"/utf8>>;

        {indexed, 4} ->
            <<"\x{001B}[34m"/utf8>>;

        {indexed, 5} ->
            <<"\x{001B}[35m"/utf8>>;

        {indexed, 6} ->
            <<"\x{001B}[36m"/utf8>>;

        {indexed, 7} ->
            <<"\x{001B}[37m"/utf8>>;

        {indexed, 8} ->
            <<"\x{001B}[90m"/utf8>>;

        {indexed, 9} ->
            <<"\x{001B}[91m"/utf8>>;

        {indexed, 10} ->
            <<"\x{001B}[92m"/utf8>>;

        {indexed, 11} ->
            <<"\x{001B}[93m"/utf8>>;

        {indexed, 12} ->
            <<"\x{001B}[94m"/utf8>>;

        {indexed, 13} ->
            <<"\x{001B}[95m"/utf8>>;

        {indexed, 14} ->
            <<"\x{001B}[96m"/utf8>>;

        {indexed, 15} ->
            <<"\x{001B}[97m"/utf8>>;

        {indexed, N} ->
            <<<<"\x{001B}[38;5;"/utf8, (erlang:integer_to_binary(N))/binary>>/binary,
                "m"/utf8>>;

        {rgb, R, G, B} ->
            <<<<<<<<<<<<"\x{001B}[38;2;"/utf8,
                                    (erlang:integer_to_binary(R))/binary>>/binary,
                                ";"/utf8>>/binary,
                            (erlang:integer_to_binary(G))/binary>>/binary,
                        ";"/utf8>>/binary,
                    (erlang:integer_to_binary(B))/binary>>/binary,
                "m"/utf8>>
    end.

-file("src/etui/style.gleam", 269).
?DOC(" Background color escape sequence.\n").
-spec ansi_bg(color()) -> binary().
ansi_bg(Color) ->
    case Color of
        default ->
            <<""/utf8>>;

        {indexed, 0} ->
            <<"\x{001B}[40m"/utf8>>;

        {indexed, 1} ->
            <<"\x{001B}[41m"/utf8>>;

        {indexed, 2} ->
            <<"\x{001B}[42m"/utf8>>;

        {indexed, 3} ->
            <<"\x{001B}[43m"/utf8>>;

        {indexed, 4} ->
            <<"\x{001B}[44m"/utf8>>;

        {indexed, 5} ->
            <<"\x{001B}[45m"/utf8>>;

        {indexed, 6} ->
            <<"\x{001B}[46m"/utf8>>;

        {indexed, 7} ->
            <<"\x{001B}[47m"/utf8>>;

        {indexed, 8} ->
            <<"\x{001B}[100m"/utf8>>;

        {indexed, 9} ->
            <<"\x{001B}[101m"/utf8>>;

        {indexed, 10} ->
            <<"\x{001B}[102m"/utf8>>;

        {indexed, 11} ->
            <<"\x{001B}[103m"/utf8>>;

        {indexed, 12} ->
            <<"\x{001B}[104m"/utf8>>;

        {indexed, 13} ->
            <<"\x{001B}[105m"/utf8>>;

        {indexed, 14} ->
            <<"\x{001B}[106m"/utf8>>;

        {indexed, 15} ->
            <<"\x{001B}[107m"/utf8>>;

        {indexed, N} ->
            <<<<"\x{001B}[48;5;"/utf8, (erlang:integer_to_binary(N))/binary>>/binary,
                "m"/utf8>>;

        {rgb, R, G, B} ->
            <<<<<<<<<<<<"\x{001B}[48;2;"/utf8,
                                    (erlang:integer_to_binary(R))/binary>>/binary,
                                ";"/utf8>>/binary,
                            (erlang:integer_to_binary(G))/binary>>/binary,
                        ";"/utf8>>/binary,
                    (erlang:integer_to_binary(B))/binary>>/binary,
                "m"/utf8>>
    end.

-file("src/etui/style.gleam", 301).
?DOC(" Text modifier escape sequence. Emits all active modifier bits.\n").
-spec ansi_modifier(modifier()) -> binary().
ansi_modifier(M) ->
    case is_none(M) of
        true ->
            <<""/utf8>>;

        false ->
            Parts = [],
            Parts@1 = case has(M, bold()) of
                true ->
                    [<<"1"/utf8>> | Parts];

                false ->
                    Parts
            end,
            Parts@2 = case has(M, dim()) of
                true ->
                    [<<"2"/utf8>> | Parts@1];

                false ->
                    Parts@1
            end,
            Parts@3 = case has(M, italic()) of
                true ->
                    [<<"3"/utf8>> | Parts@2];

                false ->
                    Parts@2
            end,
            Parts@4 = case has(M, underline()) of
                true ->
                    [<<"4"/utf8>> | Parts@3];

                false ->
                    Parts@3
            end,
            Parts@5 = case has(M, blink()) of
                true ->
                    [<<"5"/utf8>> | Parts@4];

                false ->
                    Parts@4
            end,
            Parts@6 = case has(M, reverse()) of
                true ->
                    [<<"7"/utf8>> | Parts@5];

                false ->
                    Parts@5
            end,
            Parts@7 = case has(M, strikethrough()) of
                true ->
                    [<<"9"/utf8>> | Parts@6];

                false ->
                    Parts@6
            end,
            <<<<"\x{001B}["/utf8,
                    (gleam@string:join(Parts@7, <<";"/utf8>>))/binary>>/binary,
                "m"/utf8>>
    end.

-file("src/etui/style.gleam", 340).
?DOC(" Reset all styles.\n").
-spec ansi_reset() -> binary().
ansi_reset() ->
    <<"\x{001B}[0m"/utf8>>.