Add support for chunked text and markdown outputs (#2174)

This commit is contained in:
Jonatan Kłosko 2023-08-22 13:21:22 +02:00 committed by GitHub
parent 874155db15
commit a11b1dfe7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 528 additions and 267 deletions

View file

@ -1,35 +1,47 @@
import { getAttributeOrThrow } from "../lib/attribute"; import { getAttributeOrThrow } from "../lib/attribute";
import Markdown from "../lib/markdown"; import Markdown from "../lib/markdown";
import { findChildOrThrow } from "../lib/utils";
/** /**
* A hook used to render Markdown content on the client. * A hook used to render Markdown content on the client.
* *
* ## Configuration * ## Configuration
* *
* * `data-id` - id of the renderer, under which the content event * * `data-base-path` - the path to resolve relative URLs against
* is pushed *
* * `data-allowed-uri-schemes` - a comma separated list of additional
* URI schemes that should be kept during sanitization
*
* The element should have two children:
*
* * `[data-template]` - a hidden container containing the markdown
* content. The DOM structure is ignored, only text content matters
*
* * `[data-content]` - the target element to render results into
*
*/ */
const MarkdownRenderer = { const MarkdownRenderer = {
mounted() { mounted() {
this.props = this.getProps(); this.props = this.getProps();
const markdown = new Markdown(this.el, "", { this.templateEl = findChildOrThrow(this.el, "[data-template]");
baseUrl: this.props.sessionPath, this.contentEl = findChildOrThrow(this.el, "[data-content]");
this.markdown = new Markdown(this.contentEl, this.templateEl.textContent, {
baseUrl: this.props.basePath,
allowedUriSchemes: this.props.allowedUriSchemes.split(","), allowedUriSchemes: this.props.allowedUriSchemes.split(","),
}); });
},
this.handleEvent( updated() {
`markdown_renderer:${this.props.id}:content`, this.props = this.getProps();
({ content }) => {
markdown.setContent(content); this.markdown.setContent(this.templateEl.textContent);
}
);
}, },
getProps() { getProps() {
return { return {
id: getAttributeOrThrow(this.el, "data-id"), basePath: getAttributeOrThrow(this.el, "data-base-path"),
sessionPath: getAttributeOrThrow(this.el, "data-session-path"),
allowedUriSchemes: getAttributeOrThrow( allowedUriSchemes: getAttributeOrThrow(
this.el, this.el,
"data-allowed-uri-schemes" "data-allowed-uri-schemes"

View file

@ -212,7 +212,7 @@ defmodule Livebook.LiveMarkdown.Export do
|> Enum.intersperse("\n\n") |> Enum.intersperse("\n\n")
end end
defp render_output({:stdout, text}, _ctx) do defp render_output({:terminal_text, text, %{}}, _ctx) do
text = String.replace_suffix(text, "\n", "") text = String.replace_suffix(text, "\n", "")
delimiter = MarkdownHelpers.code_block_delimiter(text) delimiter = MarkdownHelpers.code_block_delimiter(text)
text = strip_ansi(text) text = strip_ansi(text)
@ -221,14 +221,6 @@ defmodule Livebook.LiveMarkdown.Export do
|> prepend_metadata(%{output: true}) |> prepend_metadata(%{output: true})
end end
defp render_output({:text, text}, _ctx) do
delimiter = MarkdownHelpers.code_block_delimiter(text)
text = strip_ansi(text)
[delimiter, "\n", text, "\n", delimiter]
|> prepend_metadata(%{output: true})
end
defp render_output( defp render_output(
{:js, %{export: %{info_string: info_string, key: key}, js_view: %{ref: ref}}}, {:js, %{export: %{info_string: info_string, key: key}, js_view: %{ref: ref}}},
ctx ctx

View file

@ -195,7 +195,7 @@ defmodule Livebook.LiveMarkdown.Import do
[{"pre", _, [{"code", [{"class", "output"}], [output], %{}}], %{}} | ast], [{"pre", _, [{"code", [{"class", "output"}], [output], %{}}], %{}} | ast],
outputs outputs
) do ) do
take_outputs(ast, [{:text, output} | outputs]) take_outputs(ast, [{:terminal_text, output, %{chunk: false}} | outputs])
end end
defp take_outputs( defp take_outputs(
@ -206,7 +206,7 @@ defmodule Livebook.LiveMarkdown.Import do
], ],
outputs outputs
) do ) do
take_outputs(ast, [{:text, output} | outputs]) take_outputs(ast, [{:terminal_text, output, %{chunk: false}} | outputs])
end end
# Ignore other exported outputs # Ignore other exported outputs

View file

@ -666,9 +666,21 @@ defmodule Livebook.Notebook do
@doc """ @doc """
Adds new output to the given cell. Adds new output to the given cell.
Automatically merges stdout outputs and updates frames. Automatically merges terminal outputs and updates frames.
""" """
@spec add_cell_output(t(), Cell.id(), Livebook.Runtime.output()) :: t() @spec add_cell_output(t(), Cell.id(), Livebook.Runtime.output()) :: t()
def add_cell_output(notebook, cell_id, output)
# Map legacy outputs
def add_cell_output(notebook, cell_id, {:text, text}),
do: add_cell_output(notebook, cell_id, {:terminal_text, text, %{chunk: false}})
def add_cell_output(notebook, cell_id, {:plain_text, text}),
do: add_cell_output(notebook, cell_id, {:plain_text, text, %{chunk: false}})
def add_cell_output(notebook, cell_id, {:markdown, text}),
do: add_cell_output(notebook, cell_id, {:markdown, text, %{chunk: false}})
def add_cell_output(notebook, cell_id, output) do def add_cell_output(notebook, cell_id, output) do
{notebook, counter} = do_add_cell_output(notebook, cell_id, notebook.output_counter, output) {notebook, counter} = do_add_cell_output(notebook, cell_id, notebook.output_counter, output)
%{notebook | output_counter: counter} %{notebook | output_counter: counter}
@ -714,45 +726,80 @@ defmodule Livebook.Notebook do
end) end)
end end
defp apply_frame_update(_outputs, new_outputs, :replace), do: new_outputs defp apply_frame_update(_outputs, new_outputs, :replace) do
defp apply_frame_update(outputs, new_outputs, :append), do: new_outputs ++ outputs merge_chunk_outputs(new_outputs)
end
defp apply_frame_update(outputs, new_outputs, :append) do
Enum.reduce(Enum.reverse(new_outputs), outputs, &add_output(&2, &1))
end
defp add_output(outputs, {_idx, :ignored}), do: outputs defp add_output(outputs, {_idx, :ignored}), do: outputs
defp add_output([], {idx, {:stdout, text}}), # Session clients prune rendered chunks, we only keep add the new one
do: [{idx, {:stdout, normalize_stdout(text)}}] defp add_output(
[{idx, {type, :__pruned__, %{chunk: true} = info}} | tail],
defp add_output([], output), do: [output] {_idx, {type, text, %{chunk: true}}}
)
# Session clients prune stdout content and handle subsequent when type in [:terminal_text, :plain_text, :markdown] do
# ones by directly appending page content to the previous one [{idx, {type, text, info}} | tail]
defp add_output([{_idx1, {:stdout, :__pruned__}} | _] = outputs, {_idx2, {:stdout, _text}}) do
outputs
end end
# Session server keeps all outputs, so we merge consecutive stdouts # Session server keeps all outputs, so we merge consecutive chunks
defp add_output([{idx, {:stdout, text}} | tail], {_idx, {:stdout, cont}}) do defp add_output(
[{idx, {:stdout, normalize_stdout(text <> cont)}} | tail] [{idx, {:terminal_text, text, %{chunk: true} = info}} | tail],
{_idx, {:terminal_text, cont, %{chunk: true}}}
) do
[{idx, {:terminal_text, normalize_terminal_text(text <> cont), info}} | tail]
end
defp add_output(outputs, {idx, {:terminal_text, text, info}}) do
[{idx, {:terminal_text, normalize_terminal_text(text), info}} | outputs]
end
defp add_output(
[{idx, {type, text, %{chunk: true} = info}} | tail],
{_idx, {type, cont, %{chunk: true}}}
)
when type in [:plain_text, :markdown] do
[{idx, {type, normalize_terminal_text(text <> cont), info}} | tail]
end
defp add_output(outputs, {idx, {type, text, info}}) when type in [:plain_text, :markdown] do
[{idx, {type, normalize_terminal_text(text), info}} | outputs]
end
defp add_output(outputs, {idx, {type, container_outputs, info}}) when type in [:frame, :grid] do
[{idx, {type, merge_chunk_outputs(container_outputs), info}} | outputs]
end end
defp add_output(outputs, output), do: [output | outputs] defp add_output(outputs, output), do: [output | outputs]
@doc """ defp merge_chunk_outputs(outputs) do
Normalizes a text chunk coming form the standard output. outputs
|> Enum.reverse()
Handles CR rawinds and caps output lines. |> Enum.reduce([], &add_output(&2, &1))
"""
@spec normalize_stdout(String.t()) :: String.t()
def normalize_stdout(text) do
text
|> Livebook.Utils.apply_rewind()
|> Livebook.Utils.cap_lines(max_stdout_lines())
end end
@doc """ @doc """
The maximum desired number of lines of the standard output. Normalizes terminal text chunk.
Handles CR rewinds and caps output lines.
""" """
def max_stdout_lines(), do: 1_000 @spec normalize_terminal_text(String.t()) :: String.t()
def normalize_terminal_text(text) do
text
|> Livebook.Utils.apply_rewind()
|> Livebook.Utils.cap_lines(max_terminal_lines())
end
@doc """
The maximum desired number of lines of terminal text.
This is particularly relevant for standard output, which may receive
a lot of lines.
"""
def max_terminal_lines(), do: 1_000
@doc """ @doc """
Recursively adds index to all outputs, including frames. Recursively adds index to all outputs, including frames.
@ -803,60 +850,65 @@ defmodule Livebook.Notebook do
@spec prune_cell_outputs(t()) :: t() @spec prune_cell_outputs(t()) :: t()
def prune_cell_outputs(notebook) do def prune_cell_outputs(notebook) do
update_cells(notebook, fn update_cells(notebook, fn
%{outputs: _outputs} = cell -> %{cell | outputs: prune_outputs(cell.outputs)} %{outputs: _outputs} = cell -> %{cell | outputs: prune_outputs(cell.outputs, true)}
cell -> cell cell -> cell
end) end)
end end
defp prune_outputs(outputs) do defp prune_outputs(outputs, appendable?) do
outputs outputs
|> Enum.reverse() |> Enum.reverse()
|> do_prune_outputs([]) |> do_prune_outputs(appendable?, [])
end end
defp do_prune_outputs([], acc), do: acc defp do_prune_outputs([], _appendable?, acc), do: acc
# Keep the last stdout, so that we know to message it directly, but remove its contents # Keep trailing outputs that can be merged with subsequent outputs
defp do_prune_outputs([{idx, {:stdout, _}}], acc) do defp do_prune_outputs([{idx, {type, _, %{chunk: true} = info}}], true = _appendable?, acc)
[{idx, {:stdout, :__pruned__}} | acc] when type in [:terminal_text, :plain_text, :markdown] do
[{idx, {type, :__pruned__, info}} | acc]
end end
# Keep frame and its relevant contents # Keep frame and its relevant contents
defp do_prune_outputs([{idx, {:frame, frame_outputs, info}} | outputs], acc) do defp do_prune_outputs([{idx, {:frame, frame_outputs, info}} | outputs], appendable?, acc) do
do_prune_outputs(outputs, [{idx, {:frame, prune_outputs(frame_outputs), info}} | acc]) do_prune_outputs(
outputs,
appendable?,
[{idx, {:frame, prune_outputs(frame_outputs, true), info}} | acc]
)
end end
# Keep layout output and its relevant contents # Keep layout output and its relevant contents
defp do_prune_outputs([{idx, {:tabs, tabs_outputs, info}} | outputs], acc) do defp do_prune_outputs([{idx, {:tabs, tabs_outputs, info}} | outputs], appendable?, acc) do
case prune_outputs(tabs_outputs) do case prune_outputs(tabs_outputs, false) do
[] -> [] ->
do_prune_outputs(outputs, acc) do_prune_outputs(outputs, appendable?, acc)
pruned_tabs_outputs -> pruned_tabs_outputs ->
info = Map.replace(info, :labels, :__pruned__) info = Map.replace(info, :labels, :__pruned__)
do_prune_outputs(outputs, [{idx, {:tabs, pruned_tabs_outputs, info}} | acc]) do_prune_outputs(outputs, appendable?, [{idx, {:tabs, pruned_tabs_outputs, info}} | acc])
end end
end end
defp do_prune_outputs([{idx, {:grid, grid_outputs, info}} | outputs], acc) do defp do_prune_outputs([{idx, {:grid, grid_outputs, info}} | outputs], appendable?, acc) do
case prune_outputs(grid_outputs) do case prune_outputs(grid_outputs, false) do
[] -> [] ->
do_prune_outputs(outputs, acc) do_prune_outputs(outputs, appendable?, acc)
pruned_grid_outputs -> pruned_grid_outputs ->
do_prune_outputs(outputs, [{idx, {:grid, pruned_grid_outputs, info}} | acc]) do_prune_outputs(outputs, appendable?, [{idx, {:grid, pruned_grid_outputs, info}} | acc])
end end
end end
# Keep outputs that get re-rendered # Keep outputs that get re-rendered
defp do_prune_outputs([{idx, output} | outputs], acc) defp do_prune_outputs([{idx, output} | outputs], appendable?, acc)
when elem(output, 0) in [:input, :control, :error] do when elem(output, 0) in [:input, :control, :error] do
do_prune_outputs(outputs, [{idx, output} | acc]) do_prune_outputs(outputs, appendable?, [{idx, output} | acc])
end end
# Remove everything else # Remove everything else
defp do_prune_outputs([_output | outputs], acc) do defp do_prune_outputs([_output | outputs], appendable?, acc) do
do_prune_outputs(outputs, acc) do_prune_outputs(outputs, appendable?, acc)
end end
@doc """ @doc """

View file

@ -72,14 +72,12 @@ defprotocol Livebook.Runtime do
""" """
@type output :: @type output ::
:ignored :ignored
# IO output, adjacent such outputs are treated as a whole # Text with terminal style and ANSI support
| {:stdout, binary()} | {:terminal_text, text :: String.t(), info :: map()}
# Standalone text block otherwise matching :stdout
| {:text, binary()}
# Plain text content # Plain text content
| {:plain_text, binary()} | {:plain_text, text :: String.t(), info :: map()}
# Markdown content # Markdown content
| {:markdown, binary()} | {:markdown, text :: String.t(), info :: map()}
# A raw image in the given format # A raw image in the given format
| {:image, content :: binary(), mime_type :: binary()} | {:image, content :: binary(), mime_type :: binary()}
# JavaScript powered output # JavaScript powered output

View file

@ -76,7 +76,7 @@ defmodule Livebook.Runtime.Evaluator.Formatter do
defp to_inspect_output(value, opts \\ []) do defp to_inspect_output(value, opts \\ []) do
try do try do
inspected = inspect(value, inspect_opts(opts)) inspected = inspect(value, inspect_opts(opts))
{:text, inspected} {:terminal_text, inspected, %{chunk: false}}
catch catch
kind, error -> kind, error ->
formatted = format_error(kind, error, __STACKTRACE__) formatted = format_error(kind, error, __STACKTRACE__)
@ -172,6 +172,6 @@ defmodule Livebook.Runtime.Evaluator.Formatter do
defp erlang_to_output(value) do defp erlang_to_output(value) do
text = :io_lib.format("~p", [value]) |> IO.iodata_to_binary() text = :io_lib.format("~p", [value]) |> IO.iodata_to_binary()
{:text, text} {:terminal_text, text, %{chunk: false}}
end end
end end

View file

@ -438,7 +438,10 @@ defmodule Livebook.Runtime.Evaluator.IOProxy do
string = state.buffer |> Enum.reverse() |> Enum.join() string = state.buffer |> Enum.reverse() |> Enum.join()
if state.send_to != nil and string != "" do if state.send_to != nil and string != "" do
send(state.send_to, {:runtime_evaluation_output, state.ref, {:stdout, string}}) send(
state.send_to,
{:runtime_evaluation_output, state.ref, {:terminal_text, string, %{chunk: true}}}
)
end end
%{state | buffer: []} %{state | buffer: []}

View file

@ -37,40 +37,26 @@ defmodule LivebookWeb.Output do
""" """
end end
defp border?({:stdout, _text}), do: true defp border?({:terminal_text, _text, _info}), do: true
defp border?({:text, _text}), do: true defp border?({:plain_text, _text, _info}), do: true
defp border?({:error, _message, {:interrupt, _, _}}), do: false defp border?({:error, _message, {:interrupt, _, _}}), do: false
defp border?({:error, _message, _type}), do: true defp border?({:error, _message, _type}), do: true
defp border?({:grid, _, info}), do: Map.get(info, :boxed, false) defp border?({:grid, _, info}), do: Map.get(info, :boxed, false)
defp border?(_output), do: false defp border?(_output), do: false
defp render_output({:stdout, text}, %{id: id}) do defp render_output({:terminal_text, text, _info}, %{id: id}) do
text = if(text == :__pruned__, do: nil, else: text) text = if(text == :__pruned__, do: nil, else: text)
live_component(Output.StdoutComponent, id: id, text: text) live_component(Output.TerminalTextComponent, id: id, text: text)
end end
defp render_output({:text, text}, %{id: id}) do defp render_output({:plain_text, text, _info}, %{id: id}) do
assigns = %{id: id, text: text} text = if(text == :__pruned__, do: nil, else: text)
live_component(Output.PlainTextComponent, id: id, text: text)
~H"""
<Output.TextComponent.render id={@id} content={@text} />
"""
end end
defp render_output({:plain_text, text}, %{id: id}) do defp render_output({:markdown, text, _info}, %{id: id, session_id: session_id}) do
assigns = %{id: id, text: text} text = if(text == :__pruned__, do: nil, else: text)
live_component(Output.MarkdownComponent, id: id, session_id: session_id, text: text)
~H"""
<div id={@id} class="text-gray-700 whitespace-pre-wrap"><%= @text %></div>
"""
end
defp render_output({:markdown, markdown}, %{id: id, session_id: session_id}) do
live_component(Output.MarkdownComponent,
id: id,
session_id: session_id,
content: markdown
)
end end
defp render_output({:image, content, mime_type}, %{id: id}) do defp render_output({:image, content, mime_type}, %{id: id}) do

View file

@ -3,30 +3,41 @@ defmodule LivebookWeb.Output.MarkdownComponent do
@impl true @impl true
def mount(socket) do def mount(socket) do
{:ok, assign(socket, allowed_uri_schemes: Livebook.Config.allowed_uri_schemes())} {:ok, assign(socket, allowed_uri_schemes: Livebook.Config.allowed_uri_schemes(), chunks: 0),
temporary_assigns: [text: nil]}
end end
@impl true @impl true
def update(assigns, socket) do def update(assigns, socket) do
{text, assigns} = Map.pop(assigns, :text)
socket = assign(socket, assigns) socket = assign(socket, assigns)
{:ok, if text do
push_event(socket, "markdown_renderer:#{socket.assigns.id}:content", %{ {:ok, socket |> assign(text: text) |> update(:chunks, &(&1 + 1))}
content: socket.assigns.content else
})} {:ok, socket}
end
end end
@impl true @impl true
def render(assigns) do def render(assigns) do
~H""" ~H"""
<div <div
class="markdown"
id={"markdown-renderer-#{@id}"} id={"markdown-renderer-#{@id}"}
phx-hook="MarkdownRenderer" phx-hook="MarkdownRenderer"
data-id={@id} data-base-path={~p"/sessions/#{@session_id}"}
data-session-path={~p"/sessions/#{@session_id}"}
data-allowed-uri-schemes={Enum.join(@allowed_uri_schemes, ",")} data-allowed-uri-schemes={Enum.join(@allowed_uri_schemes, ",")}
> >
<div
data-template
id={"markdown-renderer-#{@id}-template"}
class="text-gray-700 whitespace-pre-wrap hidden"
phx-update="append"
phx-no-format
><span :if={@text} id={"plain-text-#{@id}-chunk-#{@chunks}"}><%=
@text %></span></div>
<div data-content class="markdown" id={"markdown-rendered-#{@id}-content"} phx-update="ignore">
</div>
</div> </div>
""" """
end end

View file

@ -0,0 +1,33 @@
defmodule LivebookWeb.Output.PlainTextComponent do
use LivebookWeb, :live_component
@impl true
def mount(socket) do
{:ok, assign(socket, chunks: 0), temporary_assigns: [text: nil]}
end
@impl true
def update(assigns, socket) do
{text, assigns} = Map.pop(assigns, :text)
socket = assign(socket, assigns)
if text do
{:ok, socket |> assign(text: text) |> update(:chunks, &(&1 + 1))}
else
{:ok, socket}
end
end
@impl true
def render(assigns) do
~H"""
<div
id={"plain-text-#{@id}"}
class="text-gray-700 whitespace-pre-wrap"
phx-update="append"
phx-no-format
><span :if={@text} id={"plain-text-#{@id}-chunk-#{@chunks}"}><%=
@text %></span></div>
"""
end
end

View file

@ -1,4 +1,4 @@
defmodule LivebookWeb.Output.StdoutComponent do defmodule LivebookWeb.Output.TerminalTextComponent do
use LivebookWeb, :live_component use LivebookWeb, :live_component
@impl true @impl true
@ -15,7 +15,7 @@ defmodule LivebookWeb.Output.StdoutComponent do
if text do if text do
text = (socket.assigns.last_line || "") <> text text = (socket.assigns.last_line || "") <> text
text = Livebook.Notebook.normalize_stdout(text) text = Livebook.Notebook.normalize_terminal_text(text)
last_line = last_line =
case Livebook.Utils.split_at_last_occurrence(text, "\n") do case Livebook.Utils.split_at_last_occurrence(text, "\n") do
@ -49,7 +49,7 @@ defmodule LivebookWeb.Output.StdoutComponent do
phx-hook="VirtualizedLines" phx-hook="VirtualizedLines"
data-max-height="300" data-max-height="300"
data-follow="true" data-follow="true"
data-max-lines={Livebook.Notebook.max_stdout_lines()} data-max-lines={Livebook.Notebook.max_terminal_lines()}
data-ignore-trailing-empty-line="true" data-ignore-trailing-empty-line="true"
> >
<% # Note 1: We add a newline to each element, so that multiple lines can be copied properly as element.textContent %> <% # Note 1: We add a newline to each element, so that multiple lines can be copied properly as element.textContent %>

View file

@ -1,38 +0,0 @@
defmodule LivebookWeb.Output.TextComponent do
use LivebookWeb, :live_component
@impl true
def render(assigns) do
~H"""
<div
id={"virtualized-text-#{@id}"}
class="relative"
phx-hook="VirtualizedLines"
data-max-height="300"
>
<% # Add a newline to each element, so that multiple lines can be copied properly %>
<div data-template class="hidden" id={"virtualized-text-#{@id}-template"} phx-no-format><%= for line <- ansi_string_to_html_lines(@content) do %><div data-line><%= [
line,
"\n"
] %></div><% end %></div>
<div
data-content
class="overflow-auto whitespace-pre font-editor text-gray-500 tiny-scrollbar"
id={"virtualized-text-#{@id}-content"}
phx-update="ignore"
phx-no-format
>
</div>
<div class="absolute right-2 top-0 z-10">
<button
class="icon-button bg-gray-100"
data-el-clipcopy
phx-click={JS.dispatch("lb:clipcopy", to: "#virtualized-text-#{@id}-template")}
>
<.remix_icon icon="clipboard-line" class="text-lg" />
</button>
</div>
</div>
"""
end
end

View file

@ -2783,11 +2783,19 @@ defmodule LivebookWeb.SessionLive do
data_view data_view
{:add_cell_evaluation_output, _client_id, cell_id, {:stdout, text}} -> {:add_cell_evaluation_output, _client_id, cell_id, {type, text, %{chunk: true}}}
when type in [:terminal_text, :plain_text, :markdown] ->
# Lookup in previous data to see if the output is already there # Lookup in previous data to see if the output is already there
case Notebook.fetch_cell_and_section(prev_data.notebook, cell_id) do case Notebook.fetch_cell_and_section(prev_data.notebook, cell_id) do
{:ok, %{outputs: [{idx, {:stdout, _}} | _]}, _section} -> {:ok, %{outputs: [{idx, {^type, _, %{chunk: true}}} | _]}, _section} ->
send_update(LivebookWeb.Output.StdoutComponent, id: "output-#{idx}", text: text) module =
case type do
:terminal_text -> LivebookWeb.Output.TerminalTextComponent
:plain_text -> LivebookWeb.Output.PlainTextComponent
:markdown -> LivebookWeb.Output.MarkdownComponent
end
send_update(module, id: "output-#{idx}", text: text)
data_view data_view
_ -> _ ->

View file

@ -577,7 +577,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
IO.puts("hey")\ IO.puts("hey")\
""", """,
outputs: [ outputs: [
{0, {:stdout, "hey"}} {0, {:terminal_text, "hey", %{chunk: true}}}
] ]
} }
] ]
@ -614,7 +614,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
| source: """ | source: """
IO.puts("hey")\ IO.puts("hey")\
""", """,
outputs: [{0, {:stdout, "hey"}}] outputs: [{0, {:terminal_text, "hey", %{chunk: true}}}]
} }
] ]
} }
@ -656,7 +656,10 @@ defmodule Livebook.LiveMarkdown.ExportTest do
| source: """ | source: """
IO.puts("hey")\ IO.puts("hey")\
""", """,
outputs: [{0, {:text, "\e[34m:ok\e[0m"}}, {1, {:stdout, "hey"}}] outputs: [
{0, {:terminal_text, "\e[34m:ok\e[0m", %{chunk: false}}},
{1, {:terminal_text, "hey", %{chunk: true}}}
]
} }
] ]
} }
@ -947,8 +950,8 @@ defmodule Livebook.LiveMarkdown.ExportTest do
{:tabs, {:tabs,
[ [
{1, {:markdown, "a"}}, {1, {:markdown, "a"}},
{2, {:text, "b"}}, {2, {:terminal_text, "b", %{chunk: false}}},
{3, {:text, "c"}} {3, {:terminal_text, "c", %{chunk: false}}}
], %{labels: ["A", "B", "C"]}}} ], %{labels: ["A", "B", "C"]}}}
] ]
} }
@ -994,9 +997,9 @@ defmodule Livebook.LiveMarkdown.ExportTest do
{0, {0,
{:grid, {:grid,
[ [
{1, {:text, "a"}}, {1, {:terminal_text, "a", %{chunk: false}}},
{2, {:markdown, "b"}}, {2, {:markdown, "b"}},
{3, {:text, "c"}} {3, {:terminal_text, "c", %{chunk: false}}}
], %{columns: 2}}} ], %{columns: 2}}}
] ]
} }
@ -1047,7 +1050,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
| source: """ | source: """
IO.puts("hey")\ IO.puts("hey")\
""", """,
outputs: [{0, {:stdout, "hey"}}] outputs: [{0, {:terminal_text, "hey", %{chunk: true}}}]
} }
] ]
} }
@ -1092,7 +1095,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
| source: """ | source: """
IO.puts("hey")\ IO.puts("hey")\
""", """,
outputs: [{0, {:stdout, "hey"}}] outputs: [{0, {:terminal_text, "hey", %{chunk: true}}}]
} }
] ]
} }

View file

@ -642,7 +642,10 @@ defmodule Livebook.LiveMarkdown.ImportTest do
source: """ source: """
IO.puts("hey")\ IO.puts("hey")\
""", """,
outputs: [{0, {:text, ":ok"}}, {1, {:text, "hey"}}] outputs: [
{0, {:terminal_text, ":ok", %{chunk: false}}},
{1, {:terminal_text, "hey", %{chunk: false}}}
]
} }
] ]
} }
@ -882,7 +885,10 @@ defmodule Livebook.LiveMarkdown.ImportTest do
source: """ source: """
IO.puts("hey")\ IO.puts("hey")\
""", """,
outputs: [{0, {:text, ":ok"}}, {1, {:text, "hey"}}] outputs: [
{0, {:terminal_text, ":ok", %{chunk: false}}},
{1, {:terminal_text, "hey", %{chunk: false}}}
]
} }
] ]
} }

View file

@ -282,7 +282,7 @@ defmodule Livebook.NotebookTest do
end end
describe "add_cell_output/3" do describe "add_cell_output/3" do
test "merges consecutive stdout results" do test "merges consecutive text outputs when both are chunks" do
notebook = %{ notebook = %{
Notebook.new() Notebook.new()
| sections: [ | sections: [
@ -290,7 +290,11 @@ defmodule Livebook.NotebookTest do
Section.new() Section.new()
| id: "s1", | id: "s1",
cells: [ cells: [
%{Cell.new(:code) | id: "c1", outputs: [{0, {:stdout, "Hola"}}]} %{
Cell.new(:code)
| id: "c1",
outputs: [{0, {:terminal_text, "Hola", %{chunk: true}}}]
}
] ]
} }
], ],
@ -300,13 +304,58 @@ defmodule Livebook.NotebookTest do
assert %{ assert %{
sections: [ sections: [
%{ %{
cells: [%{outputs: [{0, {:stdout, "Hola amigo!"}}]}] cells: [%{outputs: [{0, {:terminal_text, "Hola amigo!", %{chunk: true}}}]}]
} }
] ]
} = Notebook.add_cell_output(notebook, "c1", {:stdout, " amigo!"}) } =
Notebook.add_cell_output(
notebook,
"c1",
{:terminal_text, " amigo!", %{chunk: true}}
)
end end
test "normalizes individual stdout results to respect CR" do test "adds separate text outputs when they are not chunks" do
notebook = %{
Notebook.new()
| sections: [
%{
Section.new()
| id: "s1",
cells: [
%{
Cell.new(:code)
| id: "c1",
outputs: [{0, {:terminal_text, "Hola", %{chunk: false}}}]
}
]
}
],
output_counter: 1
}
assert %{
sections: [
%{
cells: [
%{
outputs: [
{1, {:terminal_text, " amigo!", %{chunk: true}}},
{0, {:terminal_text, "Hola", %{chunk: false}}}
]
}
]
}
]
} =
Notebook.add_cell_output(
notebook,
"c1",
{:terminal_text, " amigo!", %{chunk: true}}
)
end
test "normalizes individual terminal text outputs to respect CR" do
notebook = %{ notebook = %{
Notebook.new() Notebook.new()
| sections: [ | sections: [
@ -324,13 +373,18 @@ defmodule Livebook.NotebookTest do
assert %{ assert %{
sections: [ sections: [
%{ %{
cells: [%{outputs: [{0, {:stdout, "Hey"}}]}] cells: [%{outputs: [{0, {:terminal_text, "Hey", %{chunk: false}}}]}]
} }
] ]
} = Notebook.add_cell_output(notebook, "c1", {:stdout, "Hola\rHey"}) } =
Notebook.add_cell_output(
notebook,
"c1",
{:terminal_text, "Hola\rHey", %{chunk: false}}
)
end end
test "normalizes consecutive stdout results to respect CR" do test "normalizes consecutive terminal text outputs to respect CR" do
notebook = %{ notebook = %{
Notebook.new() Notebook.new()
| sections: [ | sections: [
@ -338,7 +392,11 @@ defmodule Livebook.NotebookTest do
Section.new() Section.new()
| id: "s1", | id: "s1",
cells: [ cells: [
%{Cell.new(:code) | id: "c1", outputs: [{0, {:stdout, "Hola"}}]} %{
Cell.new(:code)
| id: "c1",
outputs: [{0, {:terminal_text, "Hola", %{chunk: true}}}]
}
] ]
} }
], ],
@ -348,10 +406,15 @@ defmodule Livebook.NotebookTest do
assert %{ assert %{
sections: [ sections: [
%{ %{
cells: [%{outputs: [{0, {:stdout, "amigo!\r"}}]}] cells: [%{outputs: [{0, {:terminal_text, "amigo!\r", %{chunk: true}}}]}]
} }
] ]
} = Notebook.add_cell_output(notebook, "c1", {:stdout, "\ramigo!\r"}) } =
Notebook.add_cell_output(
notebook,
"c1",
{:terminal_text, "\ramigo!\r", %{chunk: true}}
)
end end
test "updates existing frames on frame update output" do test "updates existing frames on frame update output" do
@ -384,12 +447,16 @@ defmodule Livebook.NotebookTest do
cells: [ cells: [
%{ %{
outputs: [ outputs: [
{0, {:frame, [{2, {:text, "hola"}}], %{ref: "1", type: :default}}} {0,
{:frame, [{2, {:terminal_text, "hola", %{chunk: false}}}],
%{ref: "1", type: :default}}}
] ]
}, },
%{ %{
outputs: [ outputs: [
{1, {:frame, [{3, {:text, "hola"}}], %{ref: "1", type: :default}}} {1,
{:frame, [{3, {:terminal_text, "hola", %{chunk: false}}}],
%{ref: "1", type: :default}}}
] ]
} }
] ]
@ -399,7 +466,47 @@ defmodule Livebook.NotebookTest do
Notebook.add_cell_output( Notebook.add_cell_output(
notebook, notebook,
"c2", "c2",
{:frame, [{:text, "hola"}], %{ref: "1", type: :replace}} {:frame, [{:terminal_text, "hola", %{chunk: false}}],
%{ref: "1", type: :replace}}
)
end
test "merges chunked text outputs in a new frame" do
notebook = %{
Notebook.new()
| sections: [
%{
Section.new()
| id: "s1",
cells: [%{Cell.new(:code) | id: "c1", outputs: []}]
}
],
output_counter: 0
}
assert %{
sections: [
%{
cells: [
%{
outputs: [
{2,
{:frame, [{1, {:terminal_text, "hola amigo!", %{chunk: true}}}],
%{ref: "1", type: :default}}}
]
}
]
}
]
} =
Notebook.add_cell_output(
notebook,
"c1",
{:frame,
[
{:terminal_text, " amigo!", %{chunk: true}},
{:terminal_text, "hola", %{chunk: true}}
], %{ref: "1", type: :default}}
) )
end end
@ -424,6 +531,36 @@ defmodule Livebook.NotebookTest do
] ]
} = Notebook.add_cell_output(notebook, "c1", :ignored) } = Notebook.add_cell_output(notebook, "c1", :ignored)
end end
test "supports legacy text outputs" do
notebook = %{
Notebook.new()
| sections: [
%{
Section.new()
| id: "s1",
cells: [%{Cell.new(:code) | id: "c1", outputs: []}]
}
],
output_counter: 0
}
assert %{
sections: [
%{
cells: [%{outputs: [{0, {:terminal_text, "Hola", %{chunk: false}}}]}]
}
]
} = Notebook.add_cell_output(notebook, "c1", {:text, "Hola"})
assert %{
sections: [
%{
cells: [%{outputs: [{0, {:markdown, "Hola", %{chunk: false}}}]}]
}
]
} = Notebook.add_cell_output(notebook, "c1", {:markdown, "Hola"})
end
end end
describe "find_frame_outputs/2" do describe "find_frame_outputs/2" do

View file

@ -63,7 +63,7 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServerTest do
[] []
) )
assert_receive {:runtime_evaluation_output, :e1, {:stdout, output}} assert_receive {:runtime_evaluation_output, :e1, {:terminal_text, output, %{chunk: true}}}
assert output =~ "error to stdout\n" assert output =~ "error to stdout\n"
end end
@ -77,7 +77,9 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServerTest do
RuntimeServer.evaluate_code(pid, :elixir, code, {:c1, :e1}, []) RuntimeServer.evaluate_code(pid, :elixir, code, {:c1, :e1}, [])
assert_receive {:runtime_evaluation_output, :e1, {:stdout, log_message}} assert_receive {:runtime_evaluation_output, :e1,
{:terminal_text, log_message, %{chunk: true}}}
assert log_message =~ "[error] hey" assert log_message =~ "[error] hey"
end end
@ -87,7 +89,7 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServerTest do
RuntimeServer.evaluate_code(pid, :elixir, "x", {:c2, :e2}, [{:c1, :e1}]) RuntimeServer.evaluate_code(pid, :elixir, "x", {:c2, :e2}, [{:c1, :e1}])
assert_receive {:runtime_evaluation_response, :e2, {:text, "\e[34m1\e[0m"}, assert_receive {:runtime_evaluation_response, :e2, {:terminal_text, "\e[34m1\e[0m", %{}},
%{evaluation_time_ms: _time_ms}} %{evaluation_time_ms: _time_ms}}
end end

View file

@ -18,17 +18,17 @@ defmodule Livebook.Runtime.Evaluator.IOProxyTest do
describe ":stdio interoperability" do describe ":stdio interoperability" do
test "IO.puts", %{io: io} do test "IO.puts", %{io: io} do
IO.puts(io, "hey") IO.puts(io, "hey")
assert_receive {:runtime_evaluation_output, :ref, {:stdout, "hey\n"}} assert_receive {:runtime_evaluation_output, :ref, {:terminal_text, "hey\n", %{chunk: true}}}
end end
test "IO.write", %{io: io} do test "IO.write", %{io: io} do
IO.write(io, "hey") IO.write(io, "hey")
assert_receive {:runtime_evaluation_output, :ref, {:stdout, "hey"}} assert_receive {:runtime_evaluation_output, :ref, {:terminal_text, "hey", %{chunk: true}}}
end end
test "IO.inspect", %{io: io} do test "IO.inspect", %{io: io} do
IO.inspect(io, %{}, []) IO.inspect(io, %{}, [])
assert_receive {:runtime_evaluation_output, :ref, {:stdout, "%{}\n"}} assert_receive {:runtime_evaluation_output, :ref, {:terminal_text, "%{}\n", %{chunk: true}}}
end end
test "IO.read", %{io: io} do test "IO.read", %{io: io} do
@ -83,31 +83,37 @@ defmodule Livebook.Runtime.Evaluator.IOProxyTest do
test "buffers rapid output", %{io: io} do test "buffers rapid output", %{io: io} do
IO.puts(io, "hey") IO.puts(io, "hey")
IO.puts(io, "hey") IO.puts(io, "hey")
assert_receive {:runtime_evaluation_output, :ref, {:stdout, "hey\nhey\n"}}
assert_receive {:runtime_evaluation_output, :ref,
{:terminal_text, "hey\nhey\n", %{chunk: true}}}
end end
test "respects CR as line cleaner", %{io: io} do test "respects CR as line cleaner", %{io: io} do
IO.write(io, "hey") IO.write(io, "hey")
IO.write(io, "\roverride\r") IO.write(io, "\roverride\r")
assert_receive {:runtime_evaluation_output, :ref, {:stdout, "\roverride\r"}}
assert_receive {:runtime_evaluation_output, :ref,
{:terminal_text, "\roverride\r", %{chunk: true}}}
end end
test "after_evaluation/1 synchronously sends buffer contents", %{io: io} do test "after_evaluation/1 synchronously sends buffer contents", %{io: io} do
IO.puts(io, "hey") IO.puts(io, "hey")
IOProxy.after_evaluation(io) IOProxy.after_evaluation(io)
assert_received {:runtime_evaluation_output, :ref, {:stdout, "hey\n"}} assert_received {:runtime_evaluation_output, :ref, {:terminal_text, "hey\n", %{chunk: true}}}
end end
test "supports direct livebook output forwarding", %{io: io} do test "supports direct livebook output forwarding", %{io: io} do
livebook_put_output(io, {:text, "[1, 2, 3]"}) livebook_put_output(io, {:terminal_text, "[1, 2, 3]", %{chunk: false}})
assert_received {:runtime_evaluation_output, :ref, {:text, "[1, 2, 3]"}} assert_received {:runtime_evaluation_output, :ref,
{:terminal_text, "[1, 2, 3]", %{chunk: false}}}
end end
test "supports direct livebook output forwarding for a specific client", %{io: io} do test "supports direct livebook output forwarding for a specific client", %{io: io} do
livebook_put_output_to(io, "client1", {:text, "[1, 2, 3]"}) livebook_put_output_to(io, "client1", {:terminal_text, "[1, 2, 3]", %{chunk: false}})
assert_received {:runtime_evaluation_output_to, "client1", :ref, {:text, "[1, 2, 3]"}} assert_received {:runtime_evaluation_output_to, "client1", :ref,
{:terminal_text, "[1, 2, 3]", %{chunk: false}}}
end end
describe "token requests" do describe "token requests" do

View file

@ -49,8 +49,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, []) Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, [])
assert_receive {:runtime_evaluation_response, :code_1, {:text, ansi_number(3)}, assert_receive {:runtime_evaluation_response, :code_1,
metadata() = metadata} {:terminal_text, ansi_number(3), %{}}, metadata() = metadata}
assert metadata.evaluation_time_ms >= 0 assert metadata.evaluation_time_ms >= 0
@ -76,13 +76,15 @@ defmodule Livebook.Runtime.EvaluatorTest do
Evaluator.evaluate_code(evaluator, :elixir, "x", :code_2, [:code_1]) Evaluator.evaluate_code(evaluator, :elixir, "x", :code_2, [:code_1])
assert_receive {:runtime_evaluation_response, :code_2, {:text, ansi_number(1)}, metadata()} assert_receive {:runtime_evaluation_response, :code_2,
{:terminal_text, ansi_number(1), %{}}, metadata()}
end end
test "given invalid parent ref uses the default context", %{evaluator: evaluator} do test "given invalid parent ref uses the default context", %{evaluator: evaluator} do
Evaluator.evaluate_code(evaluator, :elixir, "1", :code_1, [:code_nonexistent]) Evaluator.evaluate_code(evaluator, :elixir, "1", :code_1, [:code_nonexistent])
assert_receive {:runtime_evaluation_response, :code_1, {:text, ansi_number(1)}, metadata()} assert_receive {:runtime_evaluation_response, :code_1,
{:terminal_text, ansi_number(1), %{}}, metadata()}
end end
test "given parent refs sees previous process dictionary", %{evaluator: evaluator} do test "given parent refs sees previous process dictionary", %{evaluator: evaluator} do
@ -92,10 +94,14 @@ defmodule Livebook.Runtime.EvaluatorTest do
assert_receive {:runtime_evaluation_response, :code_2, _, metadata()} assert_receive {:runtime_evaluation_response, :code_2, _, metadata()}
Evaluator.evaluate_code(evaluator, :elixir, "Process.get(:x)", :code_3, [:code_1]) Evaluator.evaluate_code(evaluator, :elixir, "Process.get(:x)", :code_3, [:code_1])
assert_receive {:runtime_evaluation_response, :code_3, {:text, ansi_number(1)}, metadata()}
assert_receive {:runtime_evaluation_response, :code_3,
{:terminal_text, ansi_number(1), %{}}, metadata()}
Evaluator.evaluate_code(evaluator, :elixir, "Process.get(:x)", :code_3, [:code_2]) Evaluator.evaluate_code(evaluator, :elixir, "Process.get(:x)", :code_3, [:code_2])
assert_receive {:runtime_evaluation_response, :code_3, {:text, ansi_number(2)}, metadata()}
assert_receive {:runtime_evaluation_response, :code_3,
{:terminal_text, ansi_number(2), %{}}, metadata()}
end end
test "keeps :rand state intact in process dictionary", %{evaluator: evaluator} do test "keeps :rand state intact in process dictionary", %{evaluator: evaluator} do
@ -103,10 +109,14 @@ defmodule Livebook.Runtime.EvaluatorTest do
assert_receive {:runtime_evaluation_response, :code_1, _, metadata()} assert_receive {:runtime_evaluation_response, :code_1, _, metadata()}
Evaluator.evaluate_code(evaluator, :elixir, ":rand.uniform()", :code_2, []) Evaluator.evaluate_code(evaluator, :elixir, ":rand.uniform()", :code_2, [])
assert_receive {:runtime_evaluation_response, :code_2, {:text, result1}, metadata()}
assert_receive {:runtime_evaluation_response, :code_2, {:terminal_text, result1, %{}},
metadata()}
Evaluator.evaluate_code(evaluator, :elixir, ":rand.uniform()", :code_2, []) Evaluator.evaluate_code(evaluator, :elixir, ":rand.uniform()", :code_2, [])
assert_receive {:runtime_evaluation_response, :code_2, {:text, result2}, metadata()}
assert_receive {:runtime_evaluation_response, :code_2, {:terminal_text, result2, %{}},
metadata()}
assert result1 != result2 assert result1 != result2
@ -114,16 +124,21 @@ defmodule Livebook.Runtime.EvaluatorTest do
assert_receive {:runtime_evaluation_response, :code_1, _, metadata()} assert_receive {:runtime_evaluation_response, :code_1, _, metadata()}
Evaluator.evaluate_code(evaluator, :elixir, ":rand.uniform()", :code_2, []) Evaluator.evaluate_code(evaluator, :elixir, ":rand.uniform()", :code_2, [])
assert_receive {:runtime_evaluation_response, :code_2, {:text, ^result1}, metadata()}
assert_receive {:runtime_evaluation_response, :code_2, {:terminal_text, ^result1, %{}},
metadata()}
Evaluator.evaluate_code(evaluator, :elixir, ":rand.uniform()", :code_2, []) Evaluator.evaluate_code(evaluator, :elixir, ":rand.uniform()", :code_2, [])
assert_receive {:runtime_evaluation_response, :code_2, {:text, ^result2}, metadata()}
assert_receive {:runtime_evaluation_response, :code_2, {:terminal_text, ^result2, %{}},
metadata()}
end end
test "captures standard output and sends it to the caller", %{evaluator: evaluator} do test "captures standard output and sends it to the caller", %{evaluator: evaluator} do
Evaluator.evaluate_code(evaluator, :elixir, ~s{IO.puts("hey")}, :code_1, []) Evaluator.evaluate_code(evaluator, :elixir, ~s{IO.puts("hey")}, :code_1, [])
assert_receive {:runtime_evaluation_output, :code_1, {:stdout, "hey\n"}} assert_receive {:runtime_evaluation_output, :code_1,
{:terminal_text, "hey\n", %{chunk: true}}}
end end
test "using livebook input sends input request to the caller", %{evaluator: evaluator} do test "using livebook input sends input request to the caller", %{evaluator: evaluator} do
@ -141,7 +156,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
assert_receive {:runtime_evaluation_input_request, :code_1, reply_to, "input1"} assert_receive {:runtime_evaluation_input_request, :code_1, reply_to, "input1"}
send(reply_to, {:runtime_evaluation_input_reply, {:ok, 10}}) send(reply_to, {:runtime_evaluation_input_reply, {:ok, 10}})
assert_receive {:runtime_evaluation_response, :code_1, {:text, ansi_number(10)}, metadata()} assert_receive {:runtime_evaluation_response, :code_1,
{:terminal_text, ansi_number(10), %{}}, metadata()}
end end
test "returns error along with its kind and stacktrace", %{evaluator: evaluator} do test "returns error along with its kind and stacktrace", %{evaluator: evaluator} do
@ -307,14 +323,18 @@ defmodule Livebook.Runtime.EvaluatorTest do
""" """
Evaluator.evaluate_code(evaluator, :elixir, code1, :code_1, []) Evaluator.evaluate_code(evaluator, :elixir, code1, :code_1, [])
assert_receive {:runtime_evaluation_response, :code_1, {:text, ansi_number(2)}, metadata()}
assert_receive {:runtime_evaluation_response, :code_1,
{:terminal_text, ansi_number(2), %{}}, metadata()}
Evaluator.evaluate_code(evaluator, :elixir, code2, :code_2, [:code_1]) Evaluator.evaluate_code(evaluator, :elixir, code2, :code_2, [:code_1])
assert_receive {:runtime_evaluation_response, :code_2, {:error, _, _}, metadata()} assert_receive {:runtime_evaluation_response, :code_2, {:error, _, _}, metadata()}
Evaluator.evaluate_code(evaluator, :elixir, code3, :code_3, [:code_2, :code_1]) Evaluator.evaluate_code(evaluator, :elixir, code3, :code_3, [:code_2, :code_1])
assert_receive {:runtime_evaluation_response, :code_3, {:text, ansi_number(4)}, metadata()}
assert_receive {:runtime_evaluation_response, :code_3,
{:terminal_text, ansi_number(4), %{}}, metadata()}
end end
test "given file option sets it in evaluation environment", %{evaluator: evaluator} do test "given file option sets it in evaluation environment", %{evaluator: evaluator} do
@ -325,8 +345,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
opts = [file: "/path/dir/file"] opts = [file: "/path/dir/file"]
Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, [], opts) Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, [], opts)
assert_receive {:runtime_evaluation_response, :code_1, {:text, ansi_string("/path/dir")}, assert_receive {:runtime_evaluation_response, :code_1,
metadata()} {:terminal_text, ansi_string("/path/dir"), %{}}, metadata()}
end end
test "kills widgets that that no evaluation points to", %{evaluator: evaluator} do test "kills widgets that that no evaluation points to", %{evaluator: evaluator} do
@ -336,8 +356,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
Evaluator.evaluate_code(evaluator, :elixir, spawn_widget_code(), :code_1, []) Evaluator.evaluate_code(evaluator, :elixir, spawn_widget_code(), :code_1, [])
assert_receive {:runtime_evaluation_response, :code_1, {:text, widget_pid1_string}, assert_receive {:runtime_evaluation_response, :code_1,
metadata()} {:terminal_text, widget_pid1_string, %{}}, metadata()}
widget_pid1 = IEx.Helpers.pid(widget_pid1_string) widget_pid1 = IEx.Helpers.pid(widget_pid1_string)
@ -345,8 +365,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
Evaluator.evaluate_code(evaluator, :elixir, spawn_widget_code(), :code_1, []) Evaluator.evaluate_code(evaluator, :elixir, spawn_widget_code(), :code_1, [])
assert_receive {:runtime_evaluation_response, :code_1, {:text, widget_pid2_string}, assert_receive {:runtime_evaluation_response, :code_1,
metadata()} {:terminal_text, widget_pid2_string, %{}}, metadata()}
widget_pid2 = IEx.Helpers.pid(widget_pid2_string) widget_pid2 = IEx.Helpers.pid(widget_pid2_string)
@ -367,8 +387,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
[] []
) )
assert_receive {:runtime_evaluation_response, :code_1, {:text, widget_pid1_string}, assert_receive {:runtime_evaluation_response, :code_1,
metadata()} {:terminal_text, widget_pid1_string, %{}}, metadata()}
widget_pid1 = IEx.Helpers.pid(widget_pid1_string) widget_pid1 = IEx.Helpers.pid(widget_pid1_string)
@ -384,11 +404,11 @@ defmodule Livebook.Runtime.EvaluatorTest do
""" """
Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, []) Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, [])
assert_receive {:runtime_evaluation_response, :code_1, {:text, _}, metadata()} assert_receive {:runtime_evaluation_response, :code_1, {:terminal_text, _, %{}}, metadata()}
# Redefining in the same evaluation works # Redefining in the same evaluation works
Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, []) Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, [])
assert_receive {:runtime_evaluation_response, :code_1, {:text, _}, metadata()} assert_receive {:runtime_evaluation_response, :code_1, {:terminal_text, _, %{}}, metadata()}
Evaluator.evaluate_code(evaluator, :elixir, code, :code_2, [], file: "file.ex") Evaluator.evaluate_code(evaluator, :elixir, code, :code_2, [], file: "file.ex")
@ -418,7 +438,7 @@ defmodule Livebook.Runtime.EvaluatorTest do
""" """
Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, []) Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, [])
assert_receive {:runtime_evaluation_response, :code_1, {:text, _}, metadata()} assert_receive {:runtime_evaluation_response, :code_1, {:terminal_text, _, %{}}, metadata()}
assert File.exists?(Path.join(ebin_path, "Elixir.Livebook.Runtime.EvaluatorTest.Disk.beam")) assert File.exists?(Path.join(ebin_path, "Elixir.Livebook.Runtime.EvaluatorTest.Disk.beam"))
@ -722,7 +742,7 @@ defmodule Livebook.Runtime.EvaluatorTest do
ref = eval_idx ref = eval_idx
parent_refs = Enum.to_list((eval_idx - 1)..0//-1) parent_refs = Enum.to_list((eval_idx - 1)..0//-1)
Evaluator.evaluate_code(evaluator, :elixir, code, ref, parent_refs) Evaluator.evaluate_code(evaluator, :elixir, code, ref, parent_refs)
assert_receive {:runtime_evaluation_response, ^ref, {:text, _}, metadata} assert_receive {:runtime_evaluation_response, ^ref, {:terminal_text, _, %{}}, metadata}
%{used: metadata.identifiers_used, defined: metadata.identifiers_defined} %{used: metadata.identifiers_used, defined: metadata.identifiers_defined}
end end
@ -1138,8 +1158,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
test "kills widgets that no evaluation points to", %{evaluator: evaluator} do test "kills widgets that no evaluation points to", %{evaluator: evaluator} do
Evaluator.evaluate_code(evaluator, :elixir, spawn_widget_code(), :code_1, []) Evaluator.evaluate_code(evaluator, :elixir, spawn_widget_code(), :code_1, [])
assert_receive {:runtime_evaluation_response, :code_1, {:text, widget_pid1_string}, assert_receive {:runtime_evaluation_response, :code_1,
metadata()} {:terminal_text, widget_pid1_string, %{}}, metadata()}
widget_pid1 = IEx.Helpers.pid(widget_pid1_string) widget_pid1 = IEx.Helpers.pid(widget_pid1_string)
@ -1157,13 +1177,13 @@ defmodule Livebook.Runtime.EvaluatorTest do
""" """
Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, []) Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, [])
assert_receive {:runtime_evaluation_response, :code_1, {:text, _}, metadata()} assert_receive {:runtime_evaluation_response, :code_1, {:terminal_text, _, %{}}, metadata()}
Evaluator.forget_evaluation(evaluator, :code_1) Evaluator.forget_evaluation(evaluator, :code_1)
# Define the module in a different evaluation # Define the module in a different evaluation
Evaluator.evaluate_code(evaluator, :elixir, code, :code_2, []) Evaluator.evaluate_code(evaluator, :elixir, code, :code_2, [])
assert_receive {:runtime_evaluation_response, :code_2, {:text, _}, metadata()} assert_receive {:runtime_evaluation_response, :code_2, {:terminal_text, _, %{}}, metadata()}
end end
end end
@ -1185,7 +1205,9 @@ defmodule Livebook.Runtime.EvaluatorTest do
Evaluator.initialize_from(evaluator, parent_evaluator, [:code_1]) Evaluator.initialize_from(evaluator, parent_evaluator, [:code_1])
Evaluator.evaluate_code(evaluator, :elixir, "x", :code_2, []) Evaluator.evaluate_code(evaluator, :elixir, "x", :code_2, [])
assert_receive {:runtime_evaluation_response, :code_2, {:text, ansi_number(1)}, metadata()}
assert_receive {:runtime_evaluation_response, :code_2,
{:terminal_text, ansi_number(1), %{}}, metadata()}
end end
end end
@ -1224,7 +1246,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
[] []
) )
assert_receive {:runtime_evaluation_response, :code_1, {:text, "6"}, metadata()} assert_receive {:runtime_evaluation_response, :code_1, {:terminal_text, "6", %{}},
metadata()}
end end
test "mixed erlang/elixir bindings", %{evaluator: evaluator} do test "mixed erlang/elixir bindings", %{evaluator: evaluator} do
@ -1242,7 +1265,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
code = ~S"#{x=>1}." code = ~S"#{x=>1}."
Evaluator.evaluate_code(evaluator, :erlang, code, :code_1, [], file: "file.ex") Evaluator.evaluate_code(evaluator, :erlang, code, :code_1, [], file: "file.ex")
assert_receive {:runtime_evaluation_response, :code_1, {:text, ~S"#{x => 1}"}, metadata()} assert_receive {:runtime_evaluation_response, :code_1, {:terminal_text, ~S"#{x => 1}", %{}},
metadata()}
end end
test "does not return error marker on empty source", %{evaluator: evaluator} do test "does not return error marker on empty source", %{evaluator: evaluator} do

View file

@ -9,6 +9,7 @@ defmodule Livebook.Session.DataTest do
@eval_resp {:ok, [1, 2, 3]} @eval_resp {:ok, [1, 2, 3]}
@smart_cell_definitions [%{kind: "text", name: "Text", requirement_presets: []}] @smart_cell_definitions [%{kind: "text", name: "Text", requirement_presets: []}]
@stdout {:terminal_text, "Hello!", %{chunk: true}}
@cid "__anonymous__" @cid "__anonymous__"
defp eval_meta(opts \\ []) do defp eval_meta(opts \\ []) do
@ -78,13 +79,13 @@ defmodule Livebook.Session.DataTest do
Notebook.Section.new() Notebook.Section.new()
| id: "s1", | id: "s1",
cells: [ cells: [
%{Notebook.Cell.new(:code) | id: "c1", outputs: [{0, {:stdout, "Hello!"}}]} %{Notebook.Cell.new(:code) | id: "c1", outputs: [{0, @stdout}]}
] ]
} }
] ]
} }
assert %{notebook: %{sections: [%{cells: [%{outputs: [{0, {:stdout, "Hello!"}}]}]}]}} = assert %{notebook: %{sections: [%{cells: [%{outputs: [{0, @stdout}]}]}]}} =
Data.new(notebook: notebook) Data.new(notebook: notebook)
assert %{notebook: %{sections: [%{cells: [%{outputs: []}]}]}} = assert %{notebook: %{sections: [%{cells: [%{outputs: []}]}]}} =
@ -1819,14 +1820,14 @@ defmodule Livebook.Session.DataTest do
{:queue_cells_evaluation, @cid, ["c1"]} {:queue_cells_evaluation, @cid, ["c1"]}
]) ])
operation = {:add_cell_evaluation_output, @cid, "c1", {:stdout, "Hello!"}} operation = {:add_cell_evaluation_output, @cid, "c1", @stdout}
assert {:ok, assert {:ok,
%{ %{
notebook: %{ notebook: %{
sections: [ sections: [
%{ %{
cells: [%{outputs: [{1, {:stdout, "Hello!"}}]}] cells: [%{outputs: [{1, @stdout}]}]
} }
] ]
} }
@ -1842,14 +1843,14 @@ defmodule Livebook.Session.DataTest do
evaluate_cells_operations(["setup", "c1"]) evaluate_cells_operations(["setup", "c1"])
]) ])
operation = {:add_cell_evaluation_output, @cid, "c1", {:stdout, "Hello!"}} operation = {:add_cell_evaluation_output, @cid, "c1", @stdout}
assert {:ok, assert {:ok,
%{ %{
notebook: %{ notebook: %{
sections: [ sections: [
%{ %{
cells: [%{outputs: [{2, {:stdout, "Hello!"}}, _result]}] cells: [%{outputs: [{2, @stdout}, _result]}]
} }
] ]
} }
@ -1868,7 +1869,7 @@ defmodule Livebook.Session.DataTest do
{:notebook_saved, @cid, []} {:notebook_saved, @cid, []}
]) ])
operation = {:add_cell_evaluation_output, @cid, "c1", {:stdout, "Hello!"}} operation = {:add_cell_evaluation_output, @cid, "c1", @stdout}
assert {:ok, %{dirty: true}, []} = Data.apply_operation(data, operation) assert {:ok, %{dirty: true}, []} = Data.apply_operation(data, operation)
end end

View file

@ -187,7 +187,7 @@ defmodule Livebook.SessionTest do
| kind: "text", | kind: "text",
source: "chunk 1\n\nchunk 2", source: "chunk 1\n\nchunk 2",
chunks: [{0, 7}, {9, 7}], chunks: [{0, 7}, {9, 7}],
outputs: [{1, {:text, "Hello"}}] outputs: [{1, {:terminal_text, "Hello", %{chunk: false}}}]
} }
section = %{Notebook.Section.new() | cells: [smart_cell]} section = %{Notebook.Section.new() | cells: [smart_cell]}
@ -210,7 +210,7 @@ defmodule Livebook.SessionTest do
assert_receive {:operation, assert_receive {:operation,
{:insert_cell, _client_id, ^section_id, 1, :code, _id, {:insert_cell, _client_id, ^section_id, 1, :code, _id,
%{source: "chunk 2", outputs: [{1, {:text, "Hello"}}]}}} %{source: "chunk 2", outputs: [{1, {:terminal_text, "Hello", %{}}}]}}}
end end
test "doesn't garbage collect input values" do test "doesn't garbage collect input values" do
@ -920,8 +920,8 @@ defmodule Livebook.SessionTest do
Session.queue_cell_evaluation(session.pid, cell_id) Session.queue_cell_evaluation(session.pid, cell_id)
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, text_output}, {:add_cell_evaluation_response, _, ^cell_id,
%{evaluation_time_ms: _time_ms}}} {:terminal_text, text_output, %{}}, %{evaluation_time_ms: _time_ms}}}
assert text_output =~ "hey" assert text_output =~ "hey"
end end
@ -944,8 +944,8 @@ defmodule Livebook.SessionTest do
Session.queue_cell_evaluation(session.pid, cell_id) Session.queue_cell_evaluation(session.pid, cell_id)
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, text_output}, {:add_cell_evaluation_response, _, ^cell_id,
%{evaluation_time_ms: _time_ms}}} {:terminal_text, text_output, %{}}, %{evaluation_time_ms: _time_ms}}}
assert text_output =~ ":error" assert text_output =~ ":error"
end end

View file

@ -207,7 +207,8 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
Session.queue_cell_evaluation(session.pid, cell_id) Session.queue_cell_evaluation(session.pid, cell_id)
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, output}, _}} {:add_cell_evaluation_response, _, ^cell_id, {:terminal_text, output, %{}},
_}}
assert output == "\e[32m\"#{secret.value}\"\e[0m" assert output == "\e[32m\"#{secret.value}\"\e[0m"
end end
@ -271,7 +272,8 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
Session.queue_cell_evaluation(session.pid, cell_id) Session.queue_cell_evaluation(session.pid, cell_id)
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, output}, _}} {:add_cell_evaluation_response, _, ^cell_id, {:terminal_text, output, %{}},
_}}
assert output == "\e[32m\"#{secret.value}\"\e[0m" assert output == "\e[32m\"#{secret.value}\"\e[0m"
end end

View file

@ -221,7 +221,7 @@ defmodule LivebookWeb.SessionControllerTest do
| source: """ | source: """
IO.puts("hey")\ IO.puts("hey")\
""", """,
outputs: [{0, {:stdout, "hey"}}] outputs: [{0, {:terminal_text, "hey", %{chunk: true}}}]
} }
] ]
} }

View file

@ -81,11 +81,11 @@ defmodule LivebookWeb.AppSessionLiveTest do
| cells: [ | cells: [
%{ %{
Livebook.Notebook.Cell.new(:code) Livebook.Notebook.Cell.new(:code)
| source: source_for_output({:stdout, "Printed output"}) | source: source_for_output({:terminal_text, "Printed output", %{chunk: false}})
}, },
%{ %{
Livebook.Notebook.Cell.new(:code) Livebook.Notebook.Cell.new(:code)
| source: source_for_output({:plain_text, "Custom text"}) | source: source_for_output({:plain_text, "Custom text", %{chunk: false}})
} }
] ]
} }
@ -121,7 +121,7 @@ defmodule LivebookWeb.AppSessionLiveTest do
| cells: [ | cells: [
%{ %{
Livebook.Notebook.Cell.new(:code) Livebook.Notebook.Cell.new(:code)
| source: source_for_output({:stdout, "Printed output"}) | source: source_for_output({:terminal_text, "Printed output", %{chunk: false}})
}, },
%{ %{
Livebook.Notebook.Cell.new(:code) Livebook.Notebook.Cell.new(:code)

View file

@ -176,8 +176,8 @@ defmodule LivebookWeb.SessionLiveTest do
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id}) |> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, "\e[32m\"true\"\e[0m"}, {:add_cell_evaluation_response, _, ^cell_id,
_}} {:terminal_text, "\e[32m\"true\"\e[0m", %{chunk: false}}, _}}
end end
test "cancelling cell evaluation", %{conn: conn, session: session} do test "cancelling cell evaluation", %{conn: conn, session: session} do
@ -627,7 +627,7 @@ defmodule LivebookWeb.SessionLiveTest do
end end
describe "outputs" do describe "outputs" do
test "stdout output update", %{conn: conn, session: session} do test "chunked text output update", %{conn: conn, session: session} do
Session.subscribe(session.id) Session.subscribe(session.id)
evaluate_setup(session.pid) evaluate_setup(session.pid)
@ -636,14 +636,21 @@ defmodule LivebookWeb.SessionLiveTest do
Session.queue_cell_evaluation(session.pid, cell_id) Session.queue_cell_evaluation(session.pid, cell_id)
send(session.pid, {:runtime_evaluation_output, cell_id, {:stdout, "line 1\n"}}) send(
session.pid,
{:runtime_evaluation_output, cell_id, {:terminal_text, "line 1\n", %{chunk: true}}}
)
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}") {:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
assert render(view) =~ "line 1" assert render(view) =~ "line 1"
send(session.pid, {:runtime_evaluation_output, cell_id, {:stdout, "line 2\n"}}) send(
session.pid,
{:runtime_evaluation_output, cell_id, {:terminal_text, "line 2\n", %{chunk: true}}}
)
wait_for_session_update(session.pid) wait_for_session_update(session.pid)
# Render once, so that stdout send_update is processed # Render once, so that the send_update is processed
_ = render(view) _ = render(view)
assert render(view) =~ "line 2" assert render(view) =~ "line 2"
end end
@ -660,7 +667,7 @@ defmodule LivebookWeb.SessionLiveTest do
send( send(
session.pid, session.pid,
{:runtime_evaluation_output, cell_id, {:runtime_evaluation_output, cell_id,
{:frame, [{:text, "In frame"}], %{ref: "1", type: :default}}} {:frame, [{:terminal_text, "In frame", %{chunk: false}}], %{ref: "1", type: :default}}}
) )
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}") {:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
@ -669,7 +676,8 @@ defmodule LivebookWeb.SessionLiveTest do
send( send(
session.pid, session.pid,
{:runtime_evaluation_output, cell_id, {:runtime_evaluation_output, cell_id,
{:frame, [{:text, "Updated frame"}], %{ref: "1", type: :replace}}} {:frame, [{:terminal_text, "Updated frame", %{chunk: false}}],
%{ref: "1", type: :replace}}}
) )
wait_for_session_update(session.pid) wait_for_session_update(session.pid)
@ -696,11 +704,13 @@ defmodule LivebookWeb.SessionLiveTest do
send( send(
session.pid, session.pid,
{:runtime_evaluation_output_to, client_id, cell_id, {:stdout, "line 1\n"}} {:runtime_evaluation_output_to, client_id, cell_id,
{:terminal_text, "line 1\n", %{chunk: true}}}
) )
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_output, _, ^cell_id, {:stdout, "line 1\n"}}} {:add_cell_evaluation_output, _, ^cell_id,
{:terminal_text, "line 1\n", %{chunk: true}}}}
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}") {:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
refute render(view) =~ "line 1" refute render(view) =~ "line 1"
@ -721,11 +731,13 @@ defmodule LivebookWeb.SessionLiveTest do
send( send(
session.pid, session.pid,
{:runtime_evaluation_output_to_clients, cell_id, {:stdout, "line 1\n"}} {:runtime_evaluation_output_to_clients, cell_id,
{:terminal_text, "line 1\n", %{chunk: false}}}
) )
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_output, _, ^cell_id, {:stdout, "line 1\n"}}} {:add_cell_evaluation_output, _, ^cell_id,
{:terminal_text, "line 1\n", %{chunk: false}}}}
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}") {:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
refute render(view) =~ "line 1" refute render(view) =~ "line 1"
@ -1467,7 +1479,8 @@ defmodule LivebookWeb.SessionLiveTest do
Session.queue_cell_evaluation(session.pid, cell_id) Session.queue_cell_evaluation(session.pid, cell_id)
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, output}, _}} {:add_cell_evaluation_response, _, ^cell_id,
{:terminal_text, output, %{chunk: false}}, _}}
assert output == "\e[32m\"#{secret.value}\"\e[0m" assert output == "\e[32m\"#{secret.value}\"\e[0m"
end end
@ -1514,7 +1527,8 @@ defmodule LivebookWeb.SessionLiveTest do
Session.queue_cell_evaluation(session.pid, cell_id) Session.queue_cell_evaluation(session.pid, cell_id)
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, output}, _}} {:add_cell_evaluation_response, _, ^cell_id,
{:terminal_text, output, %{chunk: false}}, _}}
assert output == "\e[32m\"#{secret.value}\"\e[0m" assert output == "\e[32m\"#{secret.value}\"\e[0m"
end end
@ -1605,7 +1619,8 @@ defmodule LivebookWeb.SessionLiveTest do
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id}) |> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, "\e[35mnil\e[0m"}, _}} {:add_cell_evaluation_response, _, ^cell_id,
{:terminal_text, "\e[35mnil\e[0m", %{chunk: false}}, _}}
attrs = params_for(:env_var, name: "MY_AWESOME_ENV", value: "MyEnvVarValue") attrs = params_for(:env_var, name: "MY_AWESOME_ENV", value: "MyEnvVarValue")
Settings.set_env_var(attrs) Settings.set_env_var(attrs)
@ -1616,7 +1631,7 @@ defmodule LivebookWeb.SessionLiveTest do
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:add_cell_evaluation_response, _, ^cell_id,
{:text, "\e[32m\"MyEnvVarValue\"\e[0m"}, _}} {:terminal_text, "\e[32m\"MyEnvVarValue\"\e[0m", %{chunk: false}}, _}}
Settings.set_env_var(%{attrs | value: "OTHER_VALUE"}) Settings.set_env_var(%{attrs | value: "OTHER_VALUE"})
@ -1626,7 +1641,7 @@ defmodule LivebookWeb.SessionLiveTest do
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:add_cell_evaluation_response, _, ^cell_id,
{:text, "\e[32m\"OTHER_VALUE\"\e[0m"}, _}} {:terminal_text, "\e[32m\"OTHER_VALUE\"\e[0m", %{chunk: false}}, _}}
Settings.unset_env_var("MY_AWESOME_ENV") Settings.unset_env_var("MY_AWESOME_ENV")
@ -1635,7 +1650,8 @@ defmodule LivebookWeb.SessionLiveTest do
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id}) |> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, "\e[35mnil\e[0m"}, _}} {:add_cell_evaluation_response, _, ^cell_id,
{:terminal_text, "\e[35mnil\e[0m", %{chunk: false}}, _}}
end end
@tag :tmp_dir @tag :tmp_dir
@ -1669,7 +1685,8 @@ defmodule LivebookWeb.SessionLiveTest do
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id}) |> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, output}, _}} {:add_cell_evaluation_response, _, ^cell_id,
{:terminal_text, output, %{chunk: false}}, _}}
assert output == "\e[32m\"#{String.replace(expected_path, "\\", "\\\\")}\"\e[0m" assert output == "\e[32m\"#{String.replace(expected_path, "\\", "\\\\")}\"\e[0m"
@ -1680,7 +1697,8 @@ defmodule LivebookWeb.SessionLiveTest do
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id}) |> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
assert_receive {:operation, assert_receive {:operation,
{:add_cell_evaluation_response, _, ^cell_id, {:text, output}, _}} {:add_cell_evaluation_response, _, ^cell_id,
{:terminal_text, output, %{chunk: false}}, _}}
assert output == "\e[32m\"#{String.replace(initial_os_path, "\\", "\\\\")}\"\e[0m" assert output == "\e[32m\"#{String.replace(initial_os_path, "\\", "\\\\")}\"\e[0m"
end end
@ -2009,7 +2027,12 @@ defmodule LivebookWeb.SessionLiveTest do
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}") {:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
section_id = insert_section(session.pid) section_id = insert_section(session.pid)
insert_cell_with_output(session.pid, section_id, {:text, "Hello from the app!"})
insert_cell_with_output(
session.pid,
section_id,
{:terminal_text, "Hello from the app!", %{chunk: false}}
)
slug = Livebook.Utils.random_short_id() slug = Livebook.Utils.random_short_id()