From 9bc0832e037887d0d1754ac62edf26ddb74b9b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Fri, 26 Jan 2024 09:17:46 +0100 Subject: [PATCH] Make frame and text stream updates implicit (#2451) --- lib/livebook_web/live/app_session_live.ex | 8 +-- .../live/output/frame_component.ex | 23 ++++--- .../live/output/markdown_component.ex | 22 ++++-- .../live/output/plain_text_component.ex | 25 +++++-- .../live/output/terminal_text_component.ex | 68 +++++++++++-------- lib/livebook_web/live/session_live.ex | 14 ++-- 6 files changed, 98 insertions(+), 62 deletions(-) diff --git a/lib/livebook_web/live/app_session_live.ex b/lib/livebook_web/live/app_session_live.ex index bec6b3dbf..0ce993db8 100644 --- a/lib/livebook_web/live/app_session_live.ex +++ b/lib/livebook_web/live/app_session_live.ex @@ -410,11 +410,11 @@ defmodule LivebookWeb.AppSessionLive do changed_input_ids = Session.Data.changed_input_ids(data) for {{idx, frame}, cell} <- Notebook.find_frame_outputs(data.notebook, ref) do + input_views = input_views_for_cell(cell, data, changed_input_ids) + send_update(LivebookWeb.Output.FrameComponent, id: "outputs-#{idx}-output", - outputs: frame.outputs, - update_type: update_type, - input_views: input_views_for_cell(cell, data, changed_input_ids) + event: {:update, update_type, frame.outputs, input_views} ) end @@ -432,7 +432,7 @@ defmodule LivebookWeb.AppSessionLive do :markdown -> LivebookWeb.Output.MarkdownComponent end - send_update(module, id: "outputs-#{idx}-output", text: output.text) + send_update(module, id: "outputs-#{idx}-output", event: {:append, output.text}) data_view _ -> diff --git a/lib/livebook_web/live/output/frame_component.ex b/lib/livebook_web/live/output/frame_component.ex index 291d834b5..7ad14c56c 100644 --- a/lib/livebook_web/live/output/frame_component.ex +++ b/lib/livebook_web/live/output/frame_component.ex @@ -7,19 +7,11 @@ defmodule LivebookWeb.Output.FrameComponent do end @impl true - def update(assigns, socket) do - {update_type, assigns} = Map.pop(assigns, :update_type, nil) - {outputs, assigns} = Map.pop!(assigns, :outputs) - - socket = assign(socket, assigns) - - socket = assign_new(socket, :num_outputs, fn -> length(outputs) end) + def update(%{event: {:update, update_type, outputs, input_views}}, socket) do + socket = assign(socket, input_views: input_views) socket = case update_type do - nil -> - stream(socket, :outputs, stream_items(outputs)) - :replace -> socket |> assign(num_outputs: length(outputs)) @@ -34,6 +26,17 @@ defmodule LivebookWeb.Output.FrameComponent do {:ok, socket} end + def update(assigns, socket) do + {outputs, assigns} = Map.pop!(assigns, :outputs) + + socket = assign(socket, assigns) + + socket = assign_new(socket, :num_outputs, fn -> length(outputs) end) + socket = stream(socket, :outputs, stream_items(outputs)) + + {:ok, socket} + end + defp stream_items(outputs) do for {idx, output} <- Enum.reverse(outputs) do %{id: Integer.to_string(idx), idx: idx, output: output} diff --git a/lib/livebook_web/live/output/markdown_component.ex b/lib/livebook_web/live/output/markdown_component.ex index fe3139183..50483f90a 100644 --- a/lib/livebook_web/live/output/markdown_component.ex +++ b/lib/livebook_web/live/output/markdown_component.ex @@ -5,24 +5,34 @@ defmodule LivebookWeb.Output.MarkdownComponent do def mount(socket) do {:ok, socket - |> assign(allowed_uri_schemes: Livebook.Config.allowed_uri_schemes()) + |> assign(allowed_uri_schemes: Livebook.Config.allowed_uri_schemes(), initialized: false) |> stream(:chunks, [])} 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 text do - chunk = %{id: Livebook.Utils.random_long_id(), text: text} - {:ok, stream_insert(socket, :chunks, chunk)} - else + if socket.assigns.initialized do {:ok, socket} + else + {:ok, + socket + |> append_text(text) + |> assign(:initialized, true)} end end + defp append_text(socket, text) do + chunk = %{id: Livebook.Utils.random_long_id(), text: text} + stream_insert(socket, :chunks, chunk) + end + @impl true def render(assigns) do ~H""" diff --git a/lib/livebook_web/live/output/plain_text_component.ex b/lib/livebook_web/live/output/plain_text_component.ex index 912e76817..a08d4d869 100644 --- a/lib/livebook_web/live/output/plain_text_component.ex +++ b/lib/livebook_web/live/output/plain_text_component.ex @@ -3,23 +3,36 @@ defmodule LivebookWeb.Output.PlainTextComponent do @impl true def mount(socket) do - {:ok, stream(socket, :chunks, [])} + {:ok, + socket + |> assign(initialized: false) + |> stream(:chunks, [])} 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 text do - chunk = %{id: Livebook.Utils.random_long_id(), text: text} - {:ok, stream_insert(socket, :chunks, chunk)} - else + if socket.assigns.initialized do {:ok, socket} + else + {:ok, + socket + |> append_text(text) + |> assign(:initialized, true)} end end + defp append_text(socket, text) do + chunk = %{id: Livebook.Utils.random_long_id(), text: text} + stream_insert(socket, :chunks, chunk) + end + @impl true def render(assigns) do ~H""" diff --git a/lib/livebook_web/live/output/terminal_text_component.ex b/lib/livebook_web/live/output/terminal_text_component.ex index b86fbb046..98d01c540 100644 --- a/lib/livebook_web/live/output/terminal_text_component.ex +++ b/lib/livebook_web/live/output/terminal_text_component.ex @@ -5,47 +5,57 @@ defmodule LivebookWeb.Output.TerminalTextComponent do def mount(socket) do {:ok, socket - |> assign(modifiers: [], last_line: nil, last_html_line: nil) + |> 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 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) - - {:ok, - assign(socket, - last_html_line: last_html_line, - last_line: last_line, - modifiers: modifiers - )} - else + 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""" diff --git a/lib/livebook_web/live/session_live.ex b/lib/livebook_web/live/session_live.ex index c6818485d..99e19b9b4 100644 --- a/lib/livebook_web/live/session_live.ex +++ b/lib/livebook_web/live/session_live.ex @@ -2923,14 +2923,14 @@ defmodule LivebookWeb.SessionLive do changed_input_ids = Session.Data.changed_input_ids(data) for {{idx, frame}, cell} <- Notebook.find_frame_outputs(data.notebook, ref) do + # Note that we are not updating data_view to avoid re-render, + # but any change that causes frame to re-render will update + # data_view first + input_views = input_views_for_cell(cell, data, changed_input_ids) + send_update(LivebookWeb.Output.FrameComponent, id: "outputs-#{idx}-output", - outputs: frame.outputs, - update_type: update_type, - # Note that we are not updating data_view to avoid re-render, - # but any change that causes frame to re-render will update - # data_view first - input_views: input_views_for_cell(cell, data, changed_input_ids) + event: {:update, update_type, frame.outputs, input_views} ) end @@ -2948,7 +2948,7 @@ defmodule LivebookWeb.SessionLive do :markdown -> LivebookWeb.Output.MarkdownComponent end - send_update(module, id: "outputs-#{idx}-output", text: output.text) + send_update(module, id: "outputs-#{idx}-output", event: {:append, output.text}) data_view _ ->