lib/kino/render.ex

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.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.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})
  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