Skip to main content

src/etui@keymap.erl

-module(etui@keymap).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/keymap.gleam").
-export([keymap_new/0, bind/4, unbind/2, merge/2, lookup/2, help_lines/1, all_bindings/1, render_help/4, filter/2]).
-export_type([binding/1, keymap/1]).

-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 binding(EXR) :: {binding, binary(), EXR, binary()}.

-type keymap(EXS) :: {keymap, list(binding(EXS))}.

-file("src/etui/keymap.gleam", 52).
?DOC(" Empty keymap.\n").
-spec keymap_new() -> keymap(any()).
keymap_new() ->
    {keymap, []}.

-file("src/etui/keymap.gleam", 58).
?DOC(
    " Add a binding to the end of the keymap.\n"
    " The first matching binding wins on `lookup`.\n"
).
-spec bind(keymap(EXV), binary(), EXV, binary()) -> keymap(EXV).
bind(Km, Key, Action, Description) ->
    {keymap,
        lists:append(
            erlang:element(2, Km),
            [{binding, Key, Action, Description}]
        )}.

-file("src/etui/keymap.gleam", 70).
?DOC(" Remove all bindings for a given key.\n").
-spec unbind(keymap(EXY), binary()) -> keymap(EXY).
unbind(Km, Key) ->
    {keymap,
        gleam@list:filter(
            erlang:element(2, Km),
            fun(B) -> erlang:element(2, B) /= Key end
        )}.

-file("src/etui/keymap.gleam", 75).
?DOC(" Merge `other` into `km`. Bindings from `other` are appended.\n").
-spec merge(keymap(EYB), keymap(EYB)) -> keymap(EYB).
merge(Km, Other) ->
    {keymap, lists:append(erlang:element(2, Km), erlang:element(2, Other))}.

-file("src/etui/keymap.gleam", 83).
?DOC(" Find the action bound to `key`. Returns `Error(Nil)` if not found.\n").
-spec lookup(keymap(EYF), binary()) -> {ok, EYF} | {error, nil}.
lookup(Km, Key) ->
    case gleam@list:find(
        erlang:element(2, Km),
        fun(B) -> erlang:element(2, B) =:= Key end
    ) of
        {ok, B@1} ->
            {ok, erlang:element(3, B@1)};

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

-file("src/etui/keymap.gleam", 91).
?DOC(" All bindings as `#(key, description)` pairs, in registration order.\n").
-spec help_lines(keymap(any())) -> list({binary(), binary()}).
help_lines(Km) ->
    gleam@list:map(
        erlang:element(2, Km),
        fun(B) -> {erlang:element(2, B), erlang:element(4, B)} end
    ).

-file("src/etui/keymap.gleam", 96).
?DOC(" All bindings as `#(key, action)` pairs.\n").
-spec all_bindings(keymap(EYM)) -> list({binary(), EYM}).
all_bindings(Km) ->
    gleam@list:map(
        erlang:element(2, Km),
        fun(B) -> {erlang:element(2, B), erlang:element(3, B)} end
    ).

-file("src/etui/keymap.gleam", 129).
-spec render_help_rows(
    etui@buffer:buffer(),
    etui@geometry:rect(),
    list({binary(), binary()}),
    integer(),
    etui@style:style(),
    integer()
) -> etui@buffer:buffer().
render_help_rows(Buf, Area, Lines, Key_w, St, Row) ->
    case (Row >= erlang:element(3, erlang:element(3, Area))) orelse gleam@list:is_empty(
        Lines
    ) of
        true ->
            Buf;

        false ->
            case Lines of
                [] ->
                    Buf;

                [{Key, Desc} | Rest] ->
                    Padded_key = etui@text:pad_right(Key, Key_w),
                    Row_text = <<<<Padded_key/binary, "  "/utf8>>/binary,
                        (etui@text:truncate(
                            Desc,
                            (erlang:element(2, erlang:element(3, Area)) - Key_w)
                            - 2,
                            <<""/utf8>>
                        ))/binary>>,
                    Trimmed = etui@text:truncate(
                        Row_text,
                        erlang:element(2, erlang:element(3, Area)),
                        <<""/utf8>>
                    ),
                    Padded = etui@text:pad_right(
                        Trimmed,
                        erlang:element(2, erlang:element(3, Area))
                    ),
                    Buf2 = etui@buffer:set_string(
                        Buf,
                        {position,
                            erlang:element(2, erlang:element(2, Area)),
                            erlang:element(3, erlang:element(2, Area)) + Row},
                        Padded,
                        erlang:element(2, St),
                        erlang:element(3, St),
                        erlang:element(4, St)
                    ),
                    render_help_rows(Buf2, Area, Rest, Key_w, St, Row + 1)
            end
    end.

-file("src/etui/keymap.gleam", 107).
?DOC(
    " Render a help table into `area`.\n"
    " Each row shows `  key_col  description`. `key_col_width` is the\n"
    " minimum column width for keys (auto-computed if 0).\n"
    " Returns the buffer with the help overlay drawn.\n"
).
-spec render_help(
    etui@buffer:buffer(),
    etui@geometry:rect(),
    keymap(any()),
    etui@style:style()
) -> etui@buffer:buffer().
render_help(Buf, Area, Km, St) ->
    Lines = help_lines(Km),
    Key_w = case gleam@list:fold(
        Lines,
        0,
        fun(Acc, Pair) ->
            {K, _} = Pair,
            case etui@text:cell_width(K) > Acc of
                true ->
                    etui@text:cell_width(K);

                false ->
                    Acc
            end
        end
    ) of
        0 ->
            6;

        N ->
            N
    end,
    render_help_rows(Buf, Area, Lines, Key_w, St, 0).

-file("src/etui/keymap.gleam", 170).
?DOC(
    " Keep only bindings whose description contains `query` (case-insensitive).\n"
    " Useful for a live-filter command palette.\n"
).
-spec filter(keymap(EYS), binary()) -> keymap(EYS).
filter(Km, Query) ->
    case Query of
        <<""/utf8>> ->
            Km;

        Q ->
            Lower_q = string:lowercase(Q),
            {keymap,
                gleam@list:filter(
                    erlang:element(2, Km),
                    fun(B) ->
                        gleam_stdlib:contains_string(
                            string:lowercase(erlang:element(4, B)),
                            Lower_q
                        )
                        orelse gleam_stdlib:contains_string(
                            string:lowercase(erlang:element(2, B)),
                            Lower_q
                        )
                    end
                )}
    end.