defmodule ExTerm.TerminalBackend do
@moduledoc """
Default backend that creates a `ExTerm.IoServer` process and forwards all
callbacks to its wrapping module. By default, this is set to
`ExTerm.TerminalBackend.IoServer`.
### TerminalBackend specific options:
This option can be set in the router declaration (see `ExTerm.Router`)
- `:io_server` a module, should be a supervisable `GenServer` which
satisfies a minimal set of `ExTerm.IoServer` callbacks. The required
implementations are as follows:
- *list coming in v 0.3.0*
#### Example
```
live_term "/", pubsub_server: MyWebApp.PubSub, io_server: MyIoServer
```
### TerminalBackend.IoServer specific
This option can be set in the router declaration (see `ExTerm.Router`)
- `:terminal` a (module, function, args) for the function that should be
run inside of the terminal task. This should be a REPL that takes over
the task, and typically will be an infinite loop (return type `no_return`).
If the function terminates, the io server will suffer a fatal error and
the terminal liveview will be rendered unusable.
#### Example
```
live_term "/", pubsub_server: MyWebApp.PubSub, terminal: {MyModule, :run, []}
```
"""
alias ExTerm.Backend
alias Phoenix.PubSub
import Phoenix.Component
@behaviour Backend
@impl Backend
def on_connect(_params, %{"exterm-backend" => {__MODULE__, opts}}, socket) do
io_server = Keyword.get(opts, :io_server, ExTerm.TerminalBackend.IOServer)
opts = Keyword.put(opts, :callers, [self() | Process.get(:"$callers", [])])
{:ok, pid} = DynamicSupervisor.start_child(ExTerm.BackendSupervisor, {io_server, opts})
pubsub_server = Keyword.fetch!(opts, :pubsub_server)
pubsub_topic = io_server.pubsub_topic(pid)
PubSub.subscribe(pubsub_server, pubsub_topic)
# monitor the process, LiveView should know when its child has died.
Process.monitor(pid)
{:ok, io_server.console(pid), assign(socket, io_server: {io_server, pid})}
end
@impl Backend
def on_focus(socket), do: call(socket, :on_focus, [])
@impl Backend
def on_blur(socket), do: call(socket, :on_blur, [])
@impl Backend
def on_keydown(key, socket), do: call(socket, :on_keydown, [key])
@impl Backend
def on_keyup(key, socket), do: call(socket, :on_keyup, [key])
@impl Backend
def on_paste(string, socket), do: call(socket, :on_paste, [string])
@impl Backend
def on_event(type, payload, socket), do: call(socket, :on_event, [type, payload])
defp call(socket = %{assigns: %{io_server: {io_server, pid}}}, what, args) do
apply(io_server, what, [pid | args])
{:noreply, socket}
end
end