Packages
livebook
0.14.1
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_web/live/output/terminal_text_component.ex
defmodule LivebookWeb.Output.TerminalTextComponent do
use LivebookWeb, :live_component
@impl true
def mount(socket) do
{:ok,
socket
|> assign(modifiers: [], last_line: nil, last_html_line: nil, initialized: false)
|> stream(:html_lines, [])}
end
@impl true
def update(%{event: {:append, text}}, socket) do
{:ok, append_text(socket, text)}
end
def update(assigns, socket) do
{text, assigns} = Map.pop(assigns, :text)
socket = assign(socket, assigns)
if socket.assigns.initialized do
{:ok, socket}
else
{:ok,
socket
|> append_text(text)
|> assign(:initialized, true)}
end
end
defp append_text(socket, text) do
text = (socket.assigns.last_line || "") <> text
text = Livebook.Notebook.normalize_terminal_text(text)
last_line =
case Livebook.Utils.split_at_last_occurrence(text, "\n") do
:error -> text
{:ok, _, last_line} -> last_line
end
{html_lines, modifiers} =
LivebookWeb.ANSIHelpers.ansi_string_to_html_lines_step(text, socket.assigns.modifiers)
{html_lines, [last_html_line]} = Enum.split(html_lines, -1)
stream_items =
for html_line <- html_lines, do: %{id: Livebook.Utils.random_long_id(), html: html_line}
socket = stream(socket, :html_lines, stream_items)
assign(socket,
last_html_line: last_html_line,
last_line: last_line,
modifiers: modifiers
)
end
@impl true
def render(assigns) do
~H"""
<div
id={@id}
class="relative group/root"
phx-hook="VirtualizedLines"
data-p-max-height={hook_prop(300)}
data-p-follow={hook_prop(true)}
data-p-max-lines={hook_prop(Livebook.Notebook.max_terminal_lines())}
data-p-ignore-trailing-empty-line={hook_prop(true)}
>
<%!--
Note 1: We add a newline to each element, so that multiple lines can be copied properly as element.textContent
Note 2: We glue the tags together to avoid inserting unintended whitespace
--%>
<div data-template class="hidden" id={"#{@id}-template"} phx-no-format><div
id={"#{@id}-template-append"}
phx-update="stream"
><div :for={{dom_id, html_line} <- @streams.html_lines} id={dom_id} data-line><%= [
html_line.html,
"\n"
] %></div></div><div data-line><%= @last_html_line %></div></div>
<div
data-content
class="overflow-auto whitespace-pre font-editor text-gray-500 tiny-scrollbar"
id={"#{@id}-content"}
phx-update="ignore"
>
</div>
<div class="absolute right-2 top-0 z-10 invisible group-hover/root:visible">
<.icon_button phx-click={JS.dispatch("lb:clipcopy", to: "##{@id}-template")}>
<.remix_icon icon="clipboard-line" />
</.icon_button>
</div>
</div>
"""
end
end