diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..dd93f6c --- /dev/null +++ b/flake.lock @@ -0,0 +1,23 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 0, + "narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=", + "path": "/nix/store/sdzpqjwx7pdx6lsq6llyfqqf7hspp83c-source", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..62ca517 --- /dev/null +++ b/flake.nix @@ -0,0 +1,89 @@ +{ + outputs = { self, nixpkgs }: + let + lib = nixpkgs.lib; + systems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]; + forAllSystems = f: lib.genAttrs systems (system: f system); + in + { + devShell = forAllSystems (system: + with import nixpkgs { inherit system; config.allowUnfree = true; }; + let + erlang_28 = + let + + src = fetchFromGitHub { + rev = "lukas/kernel/shell-improvements"; + sha256 = "sha256-D7XblZX78M0BlFkzUK1w1lIM+sE1QgZ73MHTJ6d84iI="; + owner = "garazdawi"; + repo = "otp"; + }; + opensslPackage = pkgs.openssl; + in + stdenv.mkDerivation ({ + # name is used instead of pname to + # - not have to pass pnames as argument + # - have a separate pname for erlang (main module) + name = "erlang-28.0"; + + inherit src; + version = "28.0"; + + nativeBuildInputs = with pkgs; [ autoconf makeWrapper perl gnum4 libxslt libxml2 ]; + buildInputs = with pkgs; [ ncurses opensslPackage systemd ]; + debugInfo = false; + + # On some machines, parallel build reliably crashes on `GEN asn1ct_eval_ext.erl` step + enableParallelBuilding = false; + + postPatch = '' + patchShebangs make + ''; + + # For OTP 27+ we need ex_doc to build the documentation + # When ex_docSupport is enabled, grab the raw ex_doc executable from the ex_doc + # derivation. Next, patch the first line to use the escript that will be + # built during the build phase of this derivation. Finally, building the + # documentation requires the erlang-logo.png asset. + preConfigure = '' + ./otp_build autoconf + ''; + + configureFlags = [ + "--with-ssl=${lib.getOutput "out" opensslPackage}" + "--with-ssl-incl=${lib.getDev opensslPackage}" # This flag was introduced in R24 + "--enable-threads" + "--enable-smp-support" + "--enable-kernel-poll" + "--enable-hipe" + # ++ optional wxSupport "--enable-wx" + "--enable-systemd" + ]; + + # install-docs will generate and install manpages and html docs + # (PDFs are generated only when fop is available). + + postInstall = '' + ln -s $out/lib/erlang/lib/erl_interface*/bin/erl_call $out/bin/erl_call + ''; + + # Some erlang bin/ scripts run sed and awk + postFixup = '' + wrapProgram $out/lib/erlang/bin/erl --prefix PATH ":" "${gnused}/bin/" + wrapProgram $out/lib/erlang/bin/start_erl --prefix PATH ":" "${lib.makeBinPath [ gnused gawk ]}" + ''; + + installTargets = ["install"]; + }); + in + pkgs.mkShell { + nativeBuildInputs = [ + erlang_28 + rebar3 + gleam + taplo + ]; + } + ); + }; +} diff --git a/test/human_rights.gleam b/test/human_rights.gleam index 9bf18bc..a2fd142 100644 --- a/test/human_rights.gleam +++ b/test/human_rights.gleam @@ -1,9 +1,5 @@ import gleam/int -import gleam/io -import gleam/list -import gleam/result -import gleam/string -import string_width.{Center, Left, Middle, Size} +import string_width.{Center, Middle, Size} const human_rights = " Universal Declaration of Human Rights @@ -171,11 +167,6 @@ pub fn main() { ) tui_app(initial, on_resize, on_data, view) - // io.println(string_width.limit( - // human_rights, - // to: string_width.Size(rows: 5000, columns: int.min(60, term_size.columns)), - // ellipsis: "...", - // )) } fn on_data(model: Model, chars) { @@ -215,6 +206,7 @@ fn view(model: Model) { ) } +@external(erlang, "tui_ffi", "tui_app") @external(javascript, "./tui_ffi.mjs", "tui_app") fn tui_app( init: state, diff --git a/test/tui_ffi.erl b/test/tui_ffi.erl new file mode 100644 index 0000000..d77e39f --- /dev/null +++ b/test/tui_ffi.erl @@ -0,0 +1,91 @@ +-module(tui_ffi). +-export([tui_app/4]). + +-behaviour(gen_event). +-export([init/1, handle_event/2, handle_info/2, handle_call/2, terminate/2]). +-export([run_read_loop/1]). + +%% This implements tui_app using the new OTP 28 features. +%% To try this out, build erlang from this branch (or what until the PR is merged): +%% +%% Branch: https://github.com/garazdawi/otp/blob/lukas/kernel/shell-improvements +%% PR: https://github.com/erlang/otp/pull/8962 +%% +%% Also, please heart the PR <3 +%% +%% To be able to exit properly on CTRL+C, you have to invoke erlang with the +Bc flag: +%% +%% erl -pa ./build/dev/erlang/string_width/ebin -pa ./build/dev/erlang/gleam_stdlib/ebin -pa ./build/dev/erlang/gleeunit/ebin -pa build/dev/erlang/filepath/ebin/ -pa build/dev/erlang/simplifile/ebin/ +Bc -noshell -s human_rights main -s init stop +%% +%% Just `gleam run` also works, but will bring up the break handler menu on CTRL+C. +%% If you abort, your terminal will still be somewhat broken; a `reset` fixes that. + +tui_app(State, OnResize, OnData, View) -> + io:setopts(standard_io, [binary, {encoding, utf8}]), + ok = shell:start_interactive({noshell, raw}), + io:put_chars(<<"\e[?1049h\e[?25l"/utf8>>), + + ok = gen_event:swap_sup_handler( + erl_signal_server, + {erl_signal_handler, []}, + {tui_ffi, [self()]}), + os:set_signal(sigwinch, handle), + %% os:set_signal(sigterm, handle), + + spawn_link(tui_ffi, run_read_loop, [self()]), + + loop(State, OnResize, OnData, View), + io:put_chars(<<"\e[?25h\e[?1049l"/utf8>>), + init:stop(), + nil. + +loop(State, OnResize, OnData, View) -> + %% io:put_chars(<<"\e[0;0H"/utf8>>), + io:put_chars(string:replace(View(State), <<"\n"/utf8>>, <<"\r\n"/utf8>>, all)), + receive + { data, Chars } -> + case handle_input(Chars, State, OnData) of + stop -> stop; + NewState -> + loop(NewState, OnResize, OnData, View) + end; + + { signal, sigint } -> stop; + { signal, sigterm } -> stop; + + { signal, sigwinch } -> + {ok, Rows} = io:rows(), + {ok, Cols} = io:columns(), + NewState = OnResize(State, Rows, Cols), + loop(NewState, OnResize, OnData, View) + end. + +handle_input(<<>>, State, _OnData) -> State; +%% handle_input(<<3, _rest/bits>>, _State, _OnData) -> stop; +handle_input(<<"q"/utf8, _rest/bits>>, _State, _OnData) -> stop; +handle_input(Rest, State, OnData) -> OnData(State, Rest). + +%% input handler + +run_read_loop(Pid) -> + Chars = io:get_chars("", 64), + Pid ! { data, Chars }, + run_read_loop(Pid). + +%% signal handler + +init({[Pid], _}) -> {ok, {Pid}}. + +handle_event(Signal, {Pid} = State) -> + Pid ! { signal, Signal }, + {ok, State}. + +handle_info(_, State) -> + {ok, State}. + +handle_call(_Request, State) -> + {ok, ok, State}. + +terminate(_Args, {Pid} = State) -> + Pid ! { signal, sigint }, + ok. diff --git a/test/tui_ffi.mjs b/test/tui_ffi.mjs index e8867a4..9a75605 100644 --- a/test/tui_ffi.mjs +++ b/test/tui_ffi.mjs @@ -5,12 +5,11 @@ export function tui_app(state, on_resize, on_data, view) { stdin.setEncoding('utf8'); function render() { - stdout.write("\x1b[0;0H") stdout.write(view(state)) } stdin.on('data', function(key) { - if (key === '\x03') { + if (key === '\x03' || key === 'q') { stdout.write("\x1b[?1049l"); process.exit() }