arizona_terminal_session (arizona v0.4.0)

View Source

Transport-agnostic orchestrator for ?terminal live views.

Mounts a ?terminal view (the calling process is its transport), then delegates the terminal policy -- key mapping, paint model, screen setup/teardown -- to an arizona_terminal_driver module. It holds no UX opinion of its own, so it serves the local TTY driver (arizona_terminal_tty) and the SSH transport (arizona_terminal_ssh) unchanged, with whatever terminal style the driver implements. Each callback dispatches to the given driver if it exports it, otherwise to arizona_terminal_default_driver's default -- so a driver overrides only what it changes.

It is parameterized by a driver module and an output function out/0 (fun((iodata()) -> ok)). The owning process becomes the live view's transport: it must forward each {arizona_push, _, _, Effects, _} it receives to handle_push/2, key reads to handle_key/2, terminal resizes to resize/3, and {arizona_reloader, reload} to handle_reload/1,2.

In dev mode (arizona_reloader:enabled/0) the owning process joins the reloader topic, and handle_reload/1,2 remounts the view on fresh code in place: a fun held in the view's state points into code a second reload purges, and a terminal client has no reconnect to fall back on.

Summary

Functions

Handles a raw key read (a character list from a local TTY, or a byte binary from a network transport). The driver maps it to commands: a stop command tears down and returns quit; event commands are dispatched to the view and the result is repainted. A read that maps to nothing returns {cont, Session} without repainting.

Handles a live-process push (the Effects from an {arizona_push, _, _, Effects, _} the owning process received): repaints via the driver, returning quit if the driver stops.

Handles a dev reload ({arizona_reloader, reload}): remounts the view with the bindings it was mounted with. See handle_reload/2.

Handles a dev reload ({arizona_reloader, reload}): stops the live view and mounts a fresh one from Bindings (a transport passes its current connection context, e.g. the latest term_rows/term_cols), then repaints it. The view's state resets, as a browser page's does on reload. Further reloads already queued are folded into this one, and so are pushes the old view queued, which describe a view that no longer exists. Returns quit if the driver stops on the repaint. A session that did not join the reloader topic (the reloader was not enabled when it started) ignores it.

The live view process backing the session.

Notifies the view of a terminal resize. Sends {term_resize, Rows, Cols} to the view's handle_info/2 (which updates bindings and re-renders); the resulting push repaints. Sent straight to the live pid (not arizona_live:send/2, which targets the live process's own mailbox).

Mounts Handler (request-free mount/1) as a live view whose transport is the calling process, creates the Driver state from DriverArg, and paints the initial frame via Out (after the driver's setup). Bindings carries any connection context (e.g. term_rows/term_cols).

Stops the live process backing the session. A transport calls this when its connection ends, so the view doesn't outlive the transport (and unsubscribes from any pubsub channels). Teardown output is emitted on the quit path, not here -- on a disconnect the client is gone, so there's nothing to write to.

Emits the driver's teardown output (e.g. restoring the cursor) via the session's output function, without touching the live process. A transport calls this to restore the client's terminal when the live view has died asynchronously -- the quit path already tears down before returning quit, but an out-of-band live-view crash never reaches that path.

Types

out()

-type out() :: fun((iodata()) -> ok).

session()

-opaque session()

Functions

handle_key/2

-spec handle_key(session(), string() | binary()) -> {cont, session()} | quit.

Handles a raw key read (a character list from a local TTY, or a byte binary from a network transport). The driver maps it to commands: a stop command tears down and returns quit; event commands are dispatched to the view and the result is repainted. A read that maps to nothing returns {cont, Session} without repainting.

handle_push(Session, Effects)

-spec handle_push(session(), [arizona_effect:cmd()]) -> {cont, session()} | quit.

Handles a live-process push (the Effects from an {arizona_push, _, _, Effects, _} the owning process received): repaints via the driver, returning quit if the driver stops.

handle_reload/1

-spec handle_reload(session()) -> {cont, session()} | quit.

Handles a dev reload ({arizona_reloader, reload}): remounts the view with the bindings it was mounted with. See handle_reload/2.

handle_reload/2

-spec handle_reload(session(), arizona_template:bindings()) -> {cont, session()} | quit.

Handles a dev reload ({arizona_reloader, reload}): stops the live view and mounts a fresh one from Bindings (a transport passes its current connection context, e.g. the latest term_rows/term_cols), then repaints it. The view's state resets, as a browser page's does on reload. Further reloads already queued are folded into this one, and so are pushes the old view queued, which describe a view that no longer exists. Returns quit if the driver stops on the repaint. A session that did not join the reloader topic (the reloader was not enabled when it started) ignores it.

pid/1

-spec pid(session()) -> pid().

The live view process backing the session.

resize/3

-spec resize(session(), pos_integer(), pos_integer()) -> ok.

Notifies the view of a terminal resize. Sends {term_resize, Rows, Cols} to the view's handle_info/2 (which updates bindings and re-renders); the resulting push repaints. Sent straight to the live pid (not arizona_live:send/2, which targets the live process's own mailbox).

start(Handler, Bindings, Driver, DriverArg, Out)

-spec start(Handler, Bindings, Driver, DriverArg, Out) -> {ok, session()} | quit
               when
                   Handler :: module(),
                   Bindings :: arizona_template:bindings(),
                   Driver :: module(),
                   DriverArg :: term(),
                   Out :: out().

Mounts Handler (request-free mount/1) as a live view whose transport is the calling process, creates the Driver state from DriverArg, and paints the initial frame via Out (after the driver's setup). Bindings carries any connection context (e.g. term_rows/term_cols).

The initial paint decides continue/stop like every other paint: a driver that stops on the very first frame gets quit instead of a session, having already emitted its teardown. Since the caller never receives a session it could stop, start/5 also stops the live process itself on that path.

stop/1

-spec stop(session()) -> ok.

Stops the live process backing the session. A transport calls this when its connection ends, so the view doesn't outlive the transport (and unsubscribes from any pubsub channels). Teardown output is emitted on the quit path, not here -- on a disconnect the client is gone, so there's nothing to write to.

teardown(Session)

-spec teardown(session()) -> ok.

Emits the driver's teardown output (e.g. restoring the cursor) via the session's output function, without touching the live process. A transport calls this to restore the client's terminal when the live view has died asynchronously -- the quit path already tears down before returning quit, but an out-of-band live-view crash never reaches that path.