A server that handles live updates in the terminal.
It partially implements The Erlang I/O Protocol,
so it is possible to use Owl.LiveScreen as an I/O device in Logger
and functions like Owl.IO.puts/2, IO.puts/2. When used as an I/O device, output is printed above dynamic blocks.
Example
require Logger
:ok = :logger.remove_handler(:default)
:ok =
:logger.add_handler(:default, :logger_std_h, %{
config: %{type: {:device, Owl.LiveScreen}},
formatter: Logger.Formatter.new(),
# Required on OTP 28+: when the handler id is `:default` and no `:filters`
# key is given, `:logger.add_handler/3` injects `filter_default: :stop`
# plus the kernel's default domain filters, which silently drop Elixir's
# `domain: [:elixir]` log events. Setting filters explicitly keeps logs
# flowing on both OTP 27 and OTP 28+.
filter_default: :log,
filters: []
})
Owl.LiveScreen.add_block(:dependency,
state: :init,
render: fn
:init -> "init..."
dependency -> ["dependency: ", Owl.Data.tag(dependency, :yellow)]
end
)
Owl.LiveScreen.add_block(:compiling,
render: fn
:init -> "init..."
filename -> ["compiling: ", Owl.Data.tag(to_string(filename), :cyan)]
end
)
["ecto", "phoenix", "ex_doc", "broadway"]
|> Enum.each(fn dependency ->
Owl.LiveScreen.update(:dependency, dependency)
1..5
|> Enum.map(&"filename#{&1}.ex")
|> Enum.each(fn filename ->
Owl.LiveScreen.update(:compiling, filename)
Process.sleep(1000)
Logger.debug("#{filename} compiled for dependency #{dependency}")
end)
end)
Summary
Functions
Adds a sticky block to the bottom of the screen that can be updated using update/3.
Waits for the next rendering.
Redirects output from :stdio to Owl.LiveScreen.
Renders data in buffer and resets the server state, removing all blocks.
Returns the current device used by the LiveScreen.
Updates the device used by LiveScreen.
Starts a server.
Renders data in buffer and terminates the server.
Updates the block state to be used in the next render iteration.
Types
@type add_block_option() :: {:state, any()} | {:render, (block_state :: any() -> Owl.Data.t())}
@type block_id() :: any()
@type start_option() :: {:name, GenServer.name()} | {:refresh_every, pos_integer()} | {:terminal_width, pos_integer() | :auto} | {:device, IO.device()}
Functions
@spec add_block(GenServer.server(), block_id(), [add_block_option()]) :: :ok
Adds a sticky block to the bottom of the screen that can be updated using update/3.
Options
:render- a function that acceptsstateand returns a view of the block. Defaults toFunction.identity/1, which meansstatemust have typeOwl.Data.t/0.:state- initial state of the block. Defaults tonil.
Example
Owl.LiveScreen.add_block(:footer, state: "starting...")
# which is equivalent to
Owl.LiveScreen.add_block(:footer, render: fn
nil -> "starting..."
data -> data
end)
@spec await_render(GenServer.server()) :: :ok
Waits for the next rendering.
Useful to ensure that the last published state is rendered on the screen.
Example
Owl.LiveScreen.add_block(:status, state: "starting...")
Owl.LiveScreen.update(:status, "done!")
Owl.LiveScreen.await_render()
@spec capture_stdio(GenServer.server(), (-> result)) :: result when result: any()
Redirects output from :stdio to Owl.LiveScreen.
Example
Owl.ProgressBar.start(id: :users, label: "Creating users", total: 100)
Owl.LiveScreen.capture_stdio(fn ->
Enum.each(1..100, fn i ->
Process.sleep(10)
Owl.ProgressBar.inc(id: :users)
# Output from the next call will be printed above the progress bar
IO.inspect([:test, i])
end)
end)
Owl.LiveScreen.await_render()
@spec flush(GenServer.server()) :: :ok
Renders data in buffer and resets the server state, removing all blocks.
The server keeps running and can accept new blocks after this call. Use this to clear the screen and start a new set of blocks without restarting the server.
@spec get_device(GenServer.server()) :: IO.device()
Returns the current device used by the LiveScreen.
@spec set_device(GenServer.server(), IO.device()) :: :ok
Updates the device used by LiveScreen.
This is useful, for example, when using the Erlang SSH console.
Example
group_leader = Process.group_leader()
Owl.LiveScreen.set_device(group_leader)
@spec start_link([start_option()]) :: GenServer.on_start()
Starts a server.
The server is started automatically by the :owl application as a named process.
Options
:name- used for name registration as described in the "Name registration" section in the documentation forGenServer. Defaults toOwl.LiveScreen.:refresh_every- the interval for refreshing the screen, in milliseconds. Defaults to 60.:terminal_width- the terminal width in characters. Defaults to:auto, which usesOwl.IO.columns/1.:device- an I/O device. Defaults to:stdio. If a terminal is not available, the server will not be started.
@spec stop(GenServer.server()) :: :ok
Renders data in buffer and terminates the server.
Unlike flush/1, the server cannot be used after this call.
@spec update(GenServer.server(), block_id(), block_state :: any()) :: :ok
Updates the block state to be used in the next render iteration.
Example
Owl.LiveScreen.add_block(:footer, state: "starting...")
Process.sleep(1000)
Owl.LiveScreen.update(:footer, "...almost done...")
Process.sleep(1000)
Owl.LiveScreen.update(:footer, "done!!!")