Harlock (harlock v0.8.0)

Copy Markdown View Source

Harlock — a pure-Elixir TUI framework for Unix terminals.

The two entry points:

  • Harlock.run/3 — blocking. Starts the app, returns when it exits.
  • Harlock.start_link/3 — non-blocking. Returns a supervisor pid for embedding inside an existing OTP application.

An app owns the terminal it runs in, so start it with mix run, not from an IEx prompt: IEx's own terminal driver reads the same tty, and the app does not receive keystrokes.

smoke/0 and smoke_crash/0 are diagnostics for the terminal layer.

Summary

Functions

Start an app and block until it exits.

Round-trip smoke test. Opens /dev/tty, writes a greeting in the alt-screen, reads up to 5 bytes from the user, echoes what was typed, and restores the terminal. Should leave the terminal in a usable state after returning.

Crash-path smoke test. Spawns a linked child that opens /dev/tty and then raises. with_raw's nested try/after blocks restore the terminal before the process dies. Returns {cleanup, reason} where cleanup is :ok if the restore ran.

Start an app under the caller's supervision tree without blocking.

Types

app()

@type app() :: module()

init_arg()

@type init_arg() :: any()

Functions

run(app, init_arg \\ nil, opts \\ [])

@spec run(app(), init_arg(), keyword()) :: {:ok, term()} | {:error, term()}

Start an app and block until it exits.

Returns:

  • {:ok, reason} when the app ends itself — update/2 returned :quit, or the runtime reported why it stopped.
  • {:error, reason} when the supervisor could not start, or went down without the app ending itself, e.g. after a crash in update/2. The terminal has been restored either way.

The caller is linked to the app supervisor, so killing the caller tears the app down and restores the terminal. The link does not work the other way: while run/3 waits it traps exits, so the app going down is a return value rather than an exit signal that kills the caller. Exit trapping is put back as it was before returning, and an abnormal exit from any other process linked to the caller still ends it, after stopping the app, as it would have without run/3 in between.

Options:

  • :theme — a Harlock.Theme.t() (or keyword list / map convertible via Harlock.Theme.build/1). Defaults to Harlock.Theme.default/0.
  • :mouse — turn on mouse reporting (default false). Clicks focus elements and press buttons and checkboxes, the wheel scrolls viewports and tables, and anything not routed arrives in update/2 as {:mouse, action, button, col, row, mods}. See Harlock.App. Off by default because while it is on the terminal's own click-and-drag text selection needs a modifier (usually Shift, or Option on macOS).

smoke()

@spec smoke() :: binary() | {:error, term()}

Round-trip smoke test. Opens /dev/tty, writes a greeting in the alt-screen, reads up to 5 bytes from the user, echoes what was typed, and restores the terminal. Should leave the terminal in a usable state after returning.

smoke_crash()

@spec smoke_crash() :: {:ok | :no_cleanup | term(), term()}

Crash-path smoke test. Spawns a linked child that opens /dev/tty and then raises. with_raw's nested try/after blocks restore the terminal before the process dies. Returns {cleanup, reason} where cleanup is :ok if the restore ran.

start_link(app, init_arg \\ nil, opts \\ [])

@spec start_link(app(), init_arg(), keyword()) :: Supervisor.on_start()

Start an app under the caller's supervision tree without blocking.

Returns {:ok, sup_pid}. The caller is responsible for handling the supervisor's lifecycle (linking, monitoring, stopping), and receives {:harlock_done, reason} when the app ends itself. Useful when embedding Harlock inside a larger OTP app that owns its terminal. The IEx caveat in the module documentation applies here too, so a dashboard in a project usually started with iex -S mix needs a separate mix run entry point.

Accepts the same options as run/3.