Skip to main content

src/etui@backend@erlang.erl

-module(etui@backend@erlang).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/backend/erlang.gleam").
-export([new/0, new_with_mouse/0]).
-export_type([erlang_terminal_state/0]).

-type erlang_terminal_state() :: {erlang_terminal_state,
        boolean(),
        integer(),
        integer(),
        boolean()}.

-file("src/etui/backend/erlang.gleam", 171).
-spec terminal_cleanup() -> nil.
terminal_cleanup() ->
    etui_terminal_ffi:uninstall_sigint_cleanup(),
    etui_terminal_ffi:write_cleanup(),
    etui_terminal_ffi:exit_raw(),
    etui_tty_state:set_raw(false),
    nil.

-file("src/etui/backend/erlang.gleam", 179).
-spec cleanup_terminal(erlang_terminal_state()) -> nil.
cleanup_terminal(_) ->
    terminal_cleanup().

-file("src/etui/backend/erlang.gleam", 159).
-spec get_terminal_size(erlang_terminal_state()) -> {ok,
        {etui@backend:terminal_size(), erlang_terminal_state()}} |
    {error, etui@backend:error()}.
get_terminal_size(State) ->
    case etui_terminal_ffi:window_size() of
        {ok, {W, H}} ->
            {ok, {{terminal_size, W - 1, H}, State}};

        {error, _} ->
            {ok, {{terminal_size, 79, 24}, State}}
    end.

-file("src/etui/backend/erlang.gleam", 233).
-spec normalise_key(binary()) -> binary().
normalise_key(Raw) ->
    case Raw of
        <<"\x{001B}[A"/utf8>> ->
            <<"up"/utf8>>;

        <<"\x{001B}OA"/utf8>> ->
            <<"up"/utf8>>;

        <<"\x{001B}[B"/utf8>> ->
            <<"down"/utf8>>;

        <<"\x{001B}OB"/utf8>> ->
            <<"down"/utf8>>;

        <<"\x{001B}[C"/utf8>> ->
            <<"right"/utf8>>;

        <<"\x{001B}OC"/utf8>> ->
            <<"right"/utf8>>;

        <<"\x{001B}[D"/utf8>> ->
            <<"left"/utf8>>;

        <<"\x{001B}OD"/utf8>> ->
            <<"left"/utf8>>;

        <<"\r"/utf8>> ->
            <<"enter"/utf8>>;

        <<"\n"/utf8>> ->
            <<"enter"/utf8>>;

        <<"\x{007F}"/utf8>> ->
            <<"backspace"/utf8>>;

        <<"\x{0008}"/utf8>> ->
            <<"backspace"/utf8>>;

        <<"\x{001B}[3~"/utf8>> ->
            <<"delete"/utf8>>;

        <<"\t"/utf8>> ->
            <<"tab"/utf8>>;

        <<"\x{001B}[Z"/utf8>> ->
            <<"backtab"/utf8>>;

        <<"\x{001B}"/utf8>> ->
            <<"esc"/utf8>>;

        <<"\x{001B}[2~"/utf8>> ->
            <<"insert"/utf8>>;

        <<"\x{001B}[5~"/utf8>> ->
            <<"pageup"/utf8>>;

        <<"\x{001B}[6~"/utf8>> ->
            <<"pagedown"/utf8>>;

        <<"\x{001B}[H"/utf8>> ->
            <<"home"/utf8>>;

        <<"\x{001B}OH"/utf8>> ->
            <<"home"/utf8>>;

        <<"\x{001B}[1~"/utf8>> ->
            <<"home"/utf8>>;

        <<"\x{001B}[F"/utf8>> ->
            <<"end"/utf8>>;

        <<"\x{001B}OF"/utf8>> ->
            <<"end"/utf8>>;

        <<"\x{001B}[4~"/utf8>> ->
            <<"end"/utf8>>;

        <<"\x{001B}[11~"/utf8>> ->
            <<"f1"/utf8>>;

        <<"\x{001B}OP"/utf8>> ->
            <<"f1"/utf8>>;

        <<"\x{001B}[12~"/utf8>> ->
            <<"f2"/utf8>>;

        <<"\x{001B}OQ"/utf8>> ->
            <<"f2"/utf8>>;

        <<"\x{001B}[13~"/utf8>> ->
            <<"f3"/utf8>>;

        <<"\x{001B}OR"/utf8>> ->
            <<"f3"/utf8>>;

        <<"\x{001B}[14~"/utf8>> ->
            <<"f4"/utf8>>;

        <<"\x{001B}OS"/utf8>> ->
            <<"f4"/utf8>>;

        <<"\x{001B}[15~"/utf8>> ->
            <<"f5"/utf8>>;

        <<"\x{001B}[17~"/utf8>> ->
            <<"f6"/utf8>>;

        <<"\x{001B}[18~"/utf8>> ->
            <<"f7"/utf8>>;

        <<"\x{001B}[19~"/utf8>> ->
            <<"f8"/utf8>>;

        <<"\x{001B}[20~"/utf8>> ->
            <<"f9"/utf8>>;

        <<"\x{001B}[21~"/utf8>> ->
            <<"f10"/utf8>>;

        <<"\x{001B}[23~"/utf8>> ->
            <<"f11"/utf8>>;

        <<"\x{001B}[24~"/utf8>> ->
            <<"f12"/utf8>>;

        <<"\x{0001}"/utf8>> ->
            <<"ctrl+a"/utf8>>;

        <<"\x{0002}"/utf8>> ->
            <<"ctrl+b"/utf8>>;

        <<"\x{0003}"/utf8>> ->
            <<"ctrl+c"/utf8>>;

        <<"\x{0004}"/utf8>> ->
            <<"ctrl+d"/utf8>>;

        <<"\x{0005}"/utf8>> ->
            <<"ctrl+e"/utf8>>;

        <<"\x{0006}"/utf8>> ->
            <<"ctrl+f"/utf8>>;

        <<"\x{0007}"/utf8>> ->
            <<"ctrl+g"/utf8>>;

        <<"\x{000B}"/utf8>> ->
            <<"ctrl+k"/utf8>>;

        <<"\x{000C}"/utf8>> ->
            <<"ctrl+l"/utf8>>;

        <<"\x{000E}"/utf8>> ->
            <<"ctrl+n"/utf8>>;

        <<"\x{000F}"/utf8>> ->
            <<"ctrl+o"/utf8>>;

        <<"\x{0010}"/utf8>> ->
            <<"ctrl+p"/utf8>>;

        <<"\x{0011}"/utf8>> ->
            <<"ctrl+q"/utf8>>;

        <<"\x{0012}"/utf8>> ->
            <<"ctrl+r"/utf8>>;

        <<"\x{0013}"/utf8>> ->
            <<"ctrl+s"/utf8>>;

        <<"\x{0014}"/utf8>> ->
            <<"ctrl+t"/utf8>>;

        <<"\x{0015}"/utf8>> ->
            <<"ctrl+u"/utf8>>;

        <<"\x{0016}"/utf8>> ->
            <<"ctrl+v"/utf8>>;

        <<"\x{0017}"/utf8>> ->
            <<"ctrl+w"/utf8>>;

        <<"\x{0018}"/utf8>> ->
            <<"ctrl+x"/utf8>>;

        <<"\x{0019}"/utf8>> ->
            <<"ctrl+y"/utf8>>;

        <<"\x{001A}"/utf8>> ->
            <<"ctrl+z"/utf8>>;

        S ->
            case gleam_stdlib:string_starts_with(S, <<"\x{001B}"/utf8>>) andalso (string:length(
                S
            )
            =:= 2) of
                true ->
                    <<"alt+"/utf8, (gleam@string:drop_start(S, 1))/binary>>;

                false ->
                    S
            end
    end.

-file("src/etui/backend/erlang.gleam", 302).
-spec parse_sgr_mouse(binary()) -> etui@backend:input_event().
parse_sgr_mouse(Payload) ->
    Is_press = gleam_stdlib:string_ends_with(Payload, <<"M"/utf8>>),
    Trimmed = case Is_press of
        true ->
            gleam@string:drop_end(Payload, 1);

        false ->
            gleam@string:drop_end(Payload, 1)
    end,
    case gleam@string:split(Trimmed, <<";"/utf8>>) of
        [Cb_str, Cx_str, Cy_str] ->
            case {gleam_stdlib:parse_int(Cb_str),
                gleam_stdlib:parse_int(Cx_str),
                gleam_stdlib:parse_int(Cy_str)} of
                {{ok, Cb}, {ok, Cx}, {ok, Cy}} ->
                    X = Cx - 1,
                    Y = Cy - 1,
                    case Cb of
                        64 ->
                            {mouse_scroll, X, Y, true};

                        65 ->
                            {mouse_scroll, X, Y, false};

                        _ ->
                            Btn = case Cb rem 4 of
                                0 ->
                                    mouse_left;

                                1 ->
                                    mouse_middle;

                                2 ->
                                    mouse_right;

                                _ ->
                                    mouse_left
                            end,
                            case Is_press of
                                true ->
                                    {mouse_press, X, Y, Btn};

                                false ->
                                    {mouse_release, X, Y, Btn}
                            end
                    end;

                {_, _, _} ->
                    {key_press, <<"\x{001B}[<"/utf8, Payload/binary>>}
            end;

        _ ->
            {key_press, <<"\x{001B}[<"/utf8, Payload/binary>>}
    end.

-file("src/etui/backend/erlang.gleam", 219).
-spec parse_input(binary()) -> etui@backend:input_event().
parse_input(Input) ->
    case Input of
        <<""/utf8>> ->
            tick;

        _ ->
            case gleam_stdlib:string_starts_with(Input, <<"\x{001B}[<"/utf8>>) of
                true ->
                    parse_sgr_mouse(gleam@string:drop_start(Input, 3));

                false ->
                    {key_press, normalise_key(Input)}
            end
    end.

-file("src/etui/backend/erlang.gleam", 137).
-spec poll_input(erlang_terminal_state(), integer()) -> {ok,
        {etui@backend:input_event(), erlang_terminal_state()}} |
    {error, etui@backend:error()}.
poll_input(State, Timeout_ms) ->
    Input_event = case etui_terminal_ffi:read_with_timeout(Timeout_ms) of
        {ok, Input} ->
            parse_input(Input);

        {error, _} ->
            tick
    end,
    case etui_terminal_ffi:window_size() of
        {ok, {C, R}} ->
            case ((C - 1) =:= erlang:element(3, State)) andalso (R =:= erlang:element(
                4,
                State
            )) of
                true ->
                    {ok, {Input_event, State}};

                false ->
                    {ok,
                        {{resize, C - 1, R},
                            {erlang_terminal_state,
                                erlang:element(2, State),
                                C - 1,
                                R,
                                erlang:element(5, State)}}}
            end;

        {error, _} ->
            {ok, {Input_event, State}}
    end.

-file("src/etui/backend/erlang.gleam", 340).
-spec int_to_string(integer()) -> binary().
int_to_string(N) ->
    case N of
        0 ->
            <<"0"/utf8>>;

        1 ->
            <<"1"/utf8>>;

        2 ->
            <<"2"/utf8>>;

        3 ->
            <<"3"/utf8>>;

        4 ->
            <<"4"/utf8>>;

        5 ->
            <<"5"/utf8>>;

        6 ->
            <<"6"/utf8>>;

        7 ->
            <<"7"/utf8>>;

        8 ->
            <<"8"/utf8>>;

        9 ->
            <<"9"/utf8>>;

        _ ->
            Digit = case N rem 10 of
                0 ->
                    <<"0"/utf8>>;

                1 ->
                    <<"1"/utf8>>;

                2 ->
                    <<"2"/utf8>>;

                3 ->
                    <<"3"/utf8>>;

                4 ->
                    <<"4"/utf8>>;

                5 ->
                    <<"5"/utf8>>;

                6 ->
                    <<"6"/utf8>>;

                7 ->
                    <<"7"/utf8>>;

                8 ->
                    <<"8"/utf8>>;

                9 ->
                    <<"9"/utf8>>;

                _ ->
                    <<"?"/utf8>>
            end,
            <<(int_to_string(N div 10))/binary, Digit/binary>>
    end.

-file("src/etui/backend/erlang.gleam", 200).
-spec render_op_to_string(etui@backend:render_op()) -> binary().
render_op_to_string(Op) ->
    case Op of
        {move_cursor, X, Y} ->
            <<<<<<<<"\x{001B}["/utf8, (int_to_string(Y + 1))/binary>>/binary,
                        ";"/utf8>>/binary,
                    (int_to_string(X + 1))/binary>>/binary,
                "H"/utf8>>;

        {write, S} ->
            S;

        clear_screen ->
            <<"\x{001B}[2J\x{001B}[H"/utf8>>;

        enter_alt_screen ->
            <<"\x{001B}[?1049h"/utf8>>;

        exit_alt_screen ->
            <<"\x{001B}[?1049l"/utf8>>;

        enable_mouse ->
            <<"\x{001B}[?1000h\x{001B}[?1006h"/utf8>>;

        disable_mouse ->
            <<"\x{001B}[?1007l\x{001B}[?1015l\x{001B}[?1006l\x{001B}[?1005l\x{001B}[?1003l\x{001B}[?1002l\x{001B}[?1000l"/utf8>>
    end.

-file("src/etui/backend/erlang.gleam", 186).
-spec write_ops_to_stdout(list(etui@backend:render_op())) -> {ok, nil} |
    {error, binary()}.
write_ops_to_stdout(Ops) ->
    Output = begin
        _pipe = Ops,
        gleam@list:fold(
            _pipe,
            <<""/utf8>>,
            fun(Acc, Op) -> <<Acc/binary, (render_op_to_string(Op))/binary>> end
        )
    end,
    case Output of
        <<""/utf8>> ->
            {ok, nil};

        S ->
            io:put_chars(S),
            {ok, nil}
    end.

-file("src/etui/backend/erlang.gleam", 127).
-spec render_ops(erlang_terminal_state(), list(etui@backend:render_op())) -> {ok,
        erlang_terminal_state()} |
    {error, etui@backend:error()}.
render_ops(State, Ops) ->
    case write_ops_to_stdout(Ops) of
        {ok, nil} ->
            {ok, State};

        {error, Reason} ->
            {error, {i_o_error, Reason}}
    end.

-file("src/etui/backend/erlang.gleam", 101).
-spec init_terminal(boolean()) -> {ok, erlang_terminal_state()} |
    {error, etui@backend:error()}.
init_terminal(Mouse) ->
    etui_tty_state:init(),
    Init_ops = case Mouse of
        true ->
            [enter_alt_screen, clear_screen, enable_mouse];

        false ->
            [enter_alt_screen, clear_screen]
    end,
    case write_ops_to_stdout(Init_ops) of
        {ok, nil} ->
            etui_terminal_ffi:enter_raw(),
            etui_tty_state:set_raw(true),
            {Cols, Rows} = case etui_terminal_ffi:window_size() of
                {ok, {C, R}} ->
                    {C - 1, R};

                {error, _} ->
                    {79, 24}
            end,
            etui_terminal_ffi:install_sigint_cleanup(
                fun() -> terminal_cleanup() end
            ),
            {ok, {erlang_terminal_state, true, Cols, Rows, Mouse}};

        {error, Reason} ->
            {error, {i_o_error, Reason}}
    end.

-file("src/etui/backend/erlang.gleam", 31).
-spec new_impl(boolean()) -> etui@backend:backend(erlang_terminal_state()).
new_impl(Mouse) ->
    {backend,
        fun() -> init_terminal(Mouse) end,
        fun render_ops/2,
        fun poll_input/2,
        fun get_terminal_size/1,
        fun cleanup_terminal/1}.

-file("src/etui/backend/erlang.gleam", 23).
-spec new() -> etui@backend:backend(erlang_terminal_state()).
new() ->
    new_impl(false).

-file("src/etui/backend/erlang.gleam", 27).
-spec new_with_mouse() -> etui@backend:backend(erlang_terminal_state()).
new_with_mouse() ->
    new_impl(true).