Skip to main content

src/etui@span.erl

-module(etui@span).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/span.gleam").
-export([span_plain/1, span_styled/2, span_link/2, with_link/2, span_fg/2, span_bg/2, span_modifier/2, span_width/1, line_new/1, line_plain/1, line_aligned/2, span_bold/1, span_italic/1, span_dim/1, span_underline/1, line_width/1, render_line/4]).
-export_type([span/0, line/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 span() :: {span,
        binary(),
        etui@style:color(),
        etui@style:color(),
        etui@style:modifier(),
        binary()}.

-type line() :: {line, list(span()), etui@text:alignment()}.

-file("src/etui/span.gleam", 46).
?DOC(" Span with default terminal colors and no modifier.\n").
-spec span_plain(binary()) -> span().
span_plain(Content) ->
    {span, Content, default, default, etui@style:none(), <<""/utf8>>}.

-file("src/etui/span.gleam", 57).
?DOC(" Span with explicit style applied.\n").
-spec span_styled(binary(), etui@style:style()) -> span().
span_styled(Content, S) ->
    {span,
        Content,
        erlang:element(2, S),
        erlang:element(3, S),
        erlang:element(4, S),
        <<""/utf8>>}.

-file("src/etui/span.gleam", 68).
?DOC(
    " Span with an OSC 8 clickable hyperlink.\n"
    " Terminals that support OSC 8 (iTerm2, Kitty, VTE, Windows Terminal) will\n"
    " render the text as a clickable link. Others display it as plain text.\n"
    "\n"
    " ```gleam\n"
    " span.span_link(\"docs.gleam.run\", \"https://docs.gleam.run\")\n"
    " ```\n"
).
-spec span_link(binary(), binary()) -> span().
span_link(Content, Uri) ->
    {span, Content, default, default, etui@style:none(), Uri}.

-file("src/etui/span.gleam", 79).
?DOC(" Add an OSC 8 hyperlink URI to an existing span.\n").
-spec with_link(span(), binary()) -> span().
with_link(Sp, Uri) ->
    {span,
        erlang:element(2, Sp),
        erlang:element(3, Sp),
        erlang:element(4, Sp),
        erlang:element(5, Sp),
        Uri}.

-file("src/etui/span.gleam", 84).
?DOC(" Set foreground color on a span.\n").
-spec span_fg(span(), etui@style:color()) -> span().
span_fg(Sp, Color) ->
    {span,
        erlang:element(2, Sp),
        Color,
        erlang:element(4, Sp),
        erlang:element(5, Sp),
        erlang:element(6, Sp)}.

-file("src/etui/span.gleam", 89).
?DOC(" Set background color on a span.\n").
-spec span_bg(span(), etui@style:color()) -> span().
span_bg(Sp, Color) ->
    {span,
        erlang:element(2, Sp),
        erlang:element(3, Sp),
        Color,
        erlang:element(5, Sp),
        erlang:element(6, Sp)}.

-file("src/etui/span.gleam", 94).
?DOC(" Add a modifier to a span.\n").
-spec span_modifier(span(), etui@style:modifier()) -> span().
span_modifier(Sp, Modifier) ->
    {span,
        erlang:element(2, Sp),
        erlang:element(3, Sp),
        erlang:element(4, Sp),
        etui@style:add(erlang:element(5, Sp), Modifier),
        erlang:element(6, Sp)}.

-file("src/etui/span.gleam", 99).
?DOC(" Total cell width of a span.\n").
-spec span_width(span()) -> integer().
span_width(Sp) ->
    etui@text:cell_width(erlang:element(2, Sp)).

-file("src/etui/span.gleam", 104).
?DOC(" Line from a list of spans, left-aligned.\n").
-spec line_new(list(span())) -> line().
line_new(Spans) ->
    {line, Spans, left}.

-file("src/etui/span.gleam", 109).
?DOC(" Line with a single unstyled string, left-aligned.\n").
-spec line_plain(binary()) -> line().
line_plain(Content) ->
    {line, [span_plain(Content)], left}.

-file("src/etui/span.gleam", 114).
?DOC(" Line from spans with explicit alignment.\n").
-spec line_aligned(list(span()), etui@text:alignment()) -> line().
line_aligned(Spans, Alignment) ->
    {line, Spans, Alignment}.

-file("src/etui/span.gleam", 119).
?DOC(" Bold span (default colors + bold modifier).\n").
-spec span_bold(binary()) -> span().
span_bold(Content) ->
    {span, Content, default, default, etui@style:bold(), <<""/utf8>>}.

-file("src/etui/span.gleam", 130).
?DOC(" Italic span (default colors + italic modifier).\n").
-spec span_italic(binary()) -> span().
span_italic(Content) ->
    {span, Content, default, default, etui@style:italic(), <<""/utf8>>}.

-file("src/etui/span.gleam", 141).
?DOC(" Dim span (default colors + dim modifier).\n").
-spec span_dim(binary()) -> span().
span_dim(Content) ->
    {span, Content, default, default, etui@style:dim(), <<""/utf8>>}.

-file("src/etui/span.gleam", 152).
?DOC(" Underline span (default colors + underline modifier).\n").
-spec span_underline(binary()) -> span().
span_underline(Content) ->
    {span, Content, default, default, etui@style:underline(), <<""/utf8>>}.

-file("src/etui/span.gleam", 163).
?DOC(" Total cell width of a line (sum of span widths).\n").
-spec line_width(line()) -> integer().
line_width(L) ->
    gleam@list:fold(
        erlang:element(2, L),
        0,
        fun(Acc, Sp) -> Acc + span_width(Sp) end
    ).

-file("src/etui/span.gleam", 195).
-spec render_spans(
    etui@buffer:buffer(),
    etui@geometry:position(),
    list(span()),
    integer(),
    integer()
) -> etui@buffer:buffer().
render_spans(Buf, Pos, Spans, X, X_end) ->
    case Spans of
        [] ->
            Buf;

        [Sp | Rest] ->
            case X >= X_end of
                true ->
                    Buf;

                false ->
                    Avail = X_end - X,
                    Content = etui@text:truncate(
                        erlang:element(2, Sp),
                        Avail,
                        <<""/utf8>>
                    ),
                    W = etui@text:cell_width(Content),
                    Buf2 = etui@buffer:set_string_linked(
                        Buf,
                        {position, X, erlang:element(3, Pos)},
                        Content,
                        erlang:element(3, Sp),
                        erlang:element(4, Sp),
                        erlang:element(5, Sp),
                        erlang:element(6, Sp)
                    ),
                    render_spans(Buf2, Pos, Rest, X + W, X_end)
            end
    end.

-file("src/etui/span.gleam", 174).
?DOC(
    " Render a line into the buffer at `pos`, clipped to `max_width` cells.\n"
    " Each span is drawn with its own fg/bg/modifier. Spans beyond max_width\n"
    " are silently dropped; a span that straddles the boundary is truncated.\n"
    " The line's `alignment` field shifts the start position within the available width.\n"
).
-spec render_line(
    etui@buffer:buffer(),
    etui@geometry:position(),
    line(),
    integer()
) -> etui@buffer:buffer().
render_line(Buf, Pos, L, Max_width) ->
    case Max_width =< 0 of
        true ->
            Buf;

        false ->
            Content_width = line_width(L),
            Offset = case erlang:element(3, L) of
                left ->
                    0;

                right ->
                    gleam@int:max(0, Max_width - Content_width);

                center ->
                    gleam@int:max(0, (Max_width - Content_width) div 2)
            end,
            Start_x = erlang:element(2, Pos) + Offset,
            render_spans(
                Buf,
                Pos,
                erlang:element(2, L),
                Start_x,
                erlang:element(2, Pos) + Max_width
            )
    end.