defprotocol Kino.Render do
@moduledoc """
Protocol defining term formatting in the context of Livebook.
"""
@fallback_to_any true
@doc """
Transforms the given value into a Livebook-compatible output.
"""
@spec to_livebook(t()) :: Kino.Output.t()
def to_livebook(value)
end
defimpl Kino.Render, for: Any do
def to_livebook(term) do
Kino.Output.inspect(term)
end
end
defimpl Kino.Render, for: Kino.Inspect do
def to_livebook(raw) do
Kino.Output.inspect(raw.term)
end
end
defimpl Kino.Render, for: Kino.JS do
def to_livebook(kino) do
info = Kino.JS.js_info(kino)
Kino.Bridge.reference_object(kino.ref, self())
Kino.Output.js(info)
end
end
defimpl Kino.Render, for: Kino.JS.Live do
def to_livebook(kino) do
Kino.Bridge.reference_object(kino.pid, self())
info = Kino.JS.Live.js_info(kino)
Kino.Output.js(info)
end
end
defimpl Kino.Render, for: Kino.Image do
def to_livebook(image) do
Kino.Output.image(image.content, image.mime_type)
end
end
defimpl Kino.Render, for: Kino.Text do
def to_livebook(text) do
Kino.Output.plain_text(text.content)
end
end
defimpl Kino.Render, for: Kino.Markdown do
def to_livebook(markdown) do
Kino.Output.markdown(markdown.content)
end
end
defimpl Kino.Render, for: Kino.Frame do
def to_livebook(kino) do
Kino.Bridge.reference_object(kino.pid, self())
outputs = Kino.Frame.get_outputs(kino)
Kino.Output.frame(outputs, %{ref: kino.ref, type: :default, placeholder: kino.placeholder})
end
end
defimpl Kino.Render, for: Kino.Layout do
def to_livebook(%{type: :tabs} = kino) do
Kino.Output.tabs(kino.outputs, kino.info)
end
def to_livebook(%{type: :grid} = kino) do
Kino.Output.grid(kino.outputs, kino.info)
end
end
defimpl Kino.Render, for: Kino.Input do
def to_livebook(input) do
Kino.Bridge.reference_object(input.attrs.ref, self())
Kino.Output.input(input.attrs)
end
end
defimpl Kino.Render, for: Kino.Control do
def to_livebook(control) do
Kino.Bridge.reference_object(control.attrs.ref, self())
Kino.Output.control(control.attrs)
end
end
# Elixir built-ins
defimpl Kino.Render, for: Reference do
def to_livebook(reference) do
cond do
accessible_ets_table?(reference) ->
reference |> Kino.ETS.new() |> Kino.Render.to_livebook()
true ->
Kino.Output.inspect(reference)
end
end
defp accessible_ets_table?(tid) do
try do
case :ets.info(tid, :protection) do
:undefined -> false
:private -> false
_ -> true
end
rescue
# When the tid is not a valid table identifier
ArgumentError -> false
end
end
end
defimpl Kino.Render, for: Atom do
def to_livebook(atom) do
cond do
application_with_supervisor?(atom) ->
raw = Kino.Inspect.new(atom)
tree = Kino.Process.app_tree(atom, direction: :left_right)
tabs = Kino.Layout.tabs(Raw: raw, "Application tree": tree)
Kino.Render.to_livebook(tabs)
Kino.Utils.supervisor?(atom) ->
raw = Kino.Inspect.new(atom)
tree = Kino.Process.sup_tree(atom, direction: :left_right)
tabs = Kino.Layout.tabs(Raw: raw, "Supervision tree": tree)
Kino.Render.to_livebook(tabs)
true ->
Kino.Output.inspect(atom)
end
end
defp application_with_supervisor?(name) do
with master when master != :undefined <- :application_controller.get_master(name),
{root, _application} when is_pid(root) <- :application_master.get_child(master),
do: true,
else: (_ -> false)
end
end
defimpl Kino.Render, for: PID do
def to_livebook(pid) do
cond do
Kino.Utils.supervisor?(pid) ->
raw = Kino.Inspect.new(pid)
tree = Kino.Process.sup_tree(pid, direction: :left_right)
tabs = Kino.Layout.tabs(Raw: raw, "Supervision tree": tree)
Kino.Render.to_livebook(tabs)
true ->
Kino.Output.inspect(pid)
end
end
end
defimpl Kino.Render, for: BitString do
def to_livebook(string) do
case Kino.Utils.get_image_type(string) do
nil ->
Kino.Output.inspect(string)
type ->
raw = Kino.Inspect.new(string)
image = Kino.Image.new(string, type)
tabs = Kino.Layout.tabs(Image: image, Raw: raw)
Kino.Render.to_livebook(tabs)
end
end
end