View Source Garnish.App behaviour (garnish v0.2.0)

Defines the Garnish.App behaviour. It provides the structure for architecting both large and small terminal applications, over SSH.

The entrypoint to the application is defined by parameters to the ssh_cli daemon option.

A Simple Example

# Assumes that `:ssh_cli` daemon option was set to:
# ssh_cli: {Garnish, {Counter.App, []}}

defmodule Counter.App do
  @behaviour Garnish.App

  # Initialize our model state
  def init(_context), do: {:ok, 0}

  # Respond to key presses
  def handle_key(%{key: key}, model) do
    case key do
      ?+ -> {:ok, model + 1}
      ?- -> {:ok, model - 1}
      _ -> {:ok, model, render: false}
  end

  # Turn the model into view
  def render(model) do
    view do
      label(content: "Counter is #{model} (+/-)")
    end
  end
end

Callbacks

The behaviour describes 3 mandatory and 3 optional callbacks. The mandatory callbacks are:

  • init/1 for setting the initial model state
  • handle_key/2 for handling keys from the client
  • render/1 for rendering the model to a view

The three optional callbacks are:

For all the handle_/2 callbacks, it is possible to disable re-rendering, even after a model update, by returning [render: false] from the callback. This is useful if the model has 'private' data that does not itself affect the view.

Summary

Callbacks

Handle an event.

Handle a key-press from the client.

Handle a resize event from the client.

The init/1 callback defines the initial model. The context is a map with the following keys

The render/1 callback defines how to render the model as a view.

Handle termination of the application.

Types

@type col_count() :: non_neg_integer()
@type context() :: %{
  args: term(),
  size: dimension(),
  environment: %{required(String.t()) => String.t()},
  connection: :ssh.connection_ref()
}
@type dimension() :: {row_count(), col_count()}
@type handle_opt() :: {:render, boolean()}
@type handle_opts() :: [handle_opt()]
@type init_opt() :: {:quit_keys, [integer()]}
@type init_opts() :: [init_opt()]
@type key_event() :: %{data: binary(), key: integer() | atom(), alt: boolean()}
@type model() :: term()
@type msg() :: term()
@type reason() :: term()
@type row_count() :: non_neg_integer()

Callbacks

Link to this callback

handle_info(term, model)

View Source (optional)
@callback handle_info(term(), model()) ::
  {:ok, model()} | {:ok, model(), handle_opts()} | {:stop, reason(), model()}

Handle an event.

Used to handle any non-key/non-resize message. This callback is optional. If not specified, the message is ignored.

Link to this callback

handle_key(key_event, model)

View Source
@callback handle_key(key_event(), model()) ::
  {:ok, model()} | {:ok, model(), handle_opts()} | {:stop, reason(), model()}

Handle a key-press from the client.

The key event is a map containing the following entries:

  • key an integer or mnenomic representing the key pressed. Most literal keys are expressed as an integer. Other keys are expressed as an atom based on their terminal settings such as :kcud1 for left-arrow. The mnemomics are derived from their terminfo specifications (see the terminfo manual page) for more details.
  • alt a boolean indicating whether the alt-key was pressed
  • data the raw data from the connection
Link to this callback

handle_resize(dimension, model)

View Source (optional)
@callback handle_resize(dimension(), model()) ::
  {:ok, model()} | {:stop, reason(), model()}

Handle a resize event from the client.

This callback is optional. If not specified, the default behaviour is render the existing model again (for the new size).

@callback init(context()) ::
  {:ok, model()} | {:ok, model(), init_opts()} | {:stop | :error, reason()}

The init/1 callback defines the initial model. The context is a map with the following keys:

  • args the args passed as passed to ssh_cli, defaults to []
  • size the size of the initial window passed as {rows, cols}
  • environment the environment passed from the client
  • connection the ssh connection reference. This may be used to query information via :ssh.connection_info/1

The callback should return the initial state on success or an error otherwise. Additionally, on success, the client may return some options that adjust the behaviour of the app. The currently supported values are:

  • quit_keys a list of integers that determine which keys will automatically close the app. If not specified, defaults to ctrl-c ([3]).
@callback render(model()) :: Garnish.Renderer.Element.t()

The render/1 callback defines how to render the model as a view.

It should return a Garnish.Renderer.Element with the :view tag. For example:

@impl true
def render(model) do
  view do
    label(content: "Hello, #{model.name}!")
  end
end
Link to this callback

terminate(reason, state)

View Source (optional)
@callback terminate(
  reason :: :normal | :shutdown | {:shutdown, term()} | term(),
  state :: term()
) :: term()

Handle termination of the application.

This callback is optional and can be used for arbitrary clean-up. Note that the client's session has already been closed by the time this callback in invoked.