mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-06 11:35:54 +08:00
Add support for chunked text and markdown outputs (#2174)
This commit is contained in:
parent
874155db15
commit
a11b1dfe7b
25 changed files with 528 additions and 267 deletions
|
@ -1,35 +1,47 @@
|
|||
import { getAttributeOrThrow } from "../lib/attribute";
|
||||
import Markdown from "../lib/markdown";
|
||||
import { findChildOrThrow } from "../lib/utils";
|
||||
|
||||
/**
|
||||
* A hook used to render Markdown content on the client.
|
||||
*
|
||||
* ## Configuration
|
||||
*
|
||||
* * `data-id` - id of the renderer, under which the content event
|
||||
* is pushed
|
||||
* * `data-base-path` - the path to resolve relative URLs against
|
||||
*
|
||||
* * `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 = {
|
||||
mounted() {
|
||||
this.props = this.getProps();
|
||||
|
||||
const markdown = new Markdown(this.el, "", {
|
||||
baseUrl: this.props.sessionPath,
|
||||
this.templateEl = findChildOrThrow(this.el, "[data-template]");
|
||||
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(","),
|
||||
});
|
||||
},
|
||||
|
||||
this.handleEvent(
|
||||
`markdown_renderer:${this.props.id}:content`,
|
||||
({ content }) => {
|
||||
markdown.setContent(content);
|
||||
}
|
||||
);
|
||||
updated() {
|
||||
this.props = this.getProps();
|
||||
|
||||
this.markdown.setContent(this.templateEl.textContent);
|
||||
},
|
||||
|
||||
getProps() {
|
||||
return {
|
||||
id: getAttributeOrThrow(this.el, "data-id"),
|
||||
sessionPath: getAttributeOrThrow(this.el, "data-session-path"),
|
||||
basePath: getAttributeOrThrow(this.el, "data-base-path"),
|
||||
allowedUriSchemes: getAttributeOrThrow(
|
||||
this.el,
|
||||
"data-allowed-uri-schemes"
|
||||
|
|
|
@ -212,7 +212,7 @@ defmodule Livebook.LiveMarkdown.Export do
|
|||
|> Enum.intersperse("\n\n")
|
||||
end
|
||||
|
||||
defp render_output({:stdout, text}, _ctx) do
|
||||
defp render_output({:terminal_text, text, %{}}, _ctx) do
|
||||
text = String.replace_suffix(text, "\n", "")
|
||||
delimiter = MarkdownHelpers.code_block_delimiter(text)
|
||||
text = strip_ansi(text)
|
||||
|
@ -221,14 +221,6 @@ defmodule Livebook.LiveMarkdown.Export do
|
|||
|> prepend_metadata(%{output: true})
|
||||
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(
|
||||
{:js, %{export: %{info_string: info_string, key: key}, js_view: %{ref: ref}}},
|
||||
ctx
|
||||
|
|
|
@ -195,7 +195,7 @@ defmodule Livebook.LiveMarkdown.Import do
|
|||
[{"pre", _, [{"code", [{"class", "output"}], [output], %{}}], %{}} | ast],
|
||||
outputs
|
||||
) do
|
||||
take_outputs(ast, [{:text, output} | outputs])
|
||||
take_outputs(ast, [{:terminal_text, output, %{chunk: false}} | outputs])
|
||||
end
|
||||
|
||||
defp take_outputs(
|
||||
|
@ -206,7 +206,7 @@ defmodule Livebook.LiveMarkdown.Import do
|
|||
],
|
||||
outputs
|
||||
) do
|
||||
take_outputs(ast, [{:text, output} | outputs])
|
||||
take_outputs(ast, [{:terminal_text, output, %{chunk: false}} | outputs])
|
||||
end
|
||||
|
||||
# Ignore other exported outputs
|
||||
|
|
|
@ -666,9 +666,21 @@ defmodule Livebook.Notebook do
|
|||
@doc """
|
||||
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()
|
||||
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
|
||||
{notebook, counter} = do_add_cell_output(notebook, cell_id, notebook.output_counter, output)
|
||||
%{notebook | output_counter: counter}
|
||||
|
@ -714,45 +726,80 @@ defmodule Livebook.Notebook do
|
|||
end)
|
||||
end
|
||||
|
||||
defp apply_frame_update(_outputs, new_outputs, :replace), do: new_outputs
|
||||
defp apply_frame_update(outputs, new_outputs, :append), do: new_outputs ++ outputs
|
||||
defp apply_frame_update(_outputs, new_outputs, :replace) do
|
||||
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([], {idx, {:stdout, text}}),
|
||||
do: [{idx, {:stdout, normalize_stdout(text)}}]
|
||||
|
||||
defp add_output([], output), do: [output]
|
||||
|
||||
# Session clients prune stdout content and handle subsequent
|
||||
# ones by directly appending page content to the previous one
|
||||
defp add_output([{_idx1, {:stdout, :__pruned__}} | _] = outputs, {_idx2, {:stdout, _text}}) do
|
||||
outputs
|
||||
# Session clients prune rendered chunks, we only keep add the new one
|
||||
defp add_output(
|
||||
[{idx, {type, :__pruned__, %{chunk: true} = info}} | tail],
|
||||
{_idx, {type, text, %{chunk: true}}}
|
||||
)
|
||||
when type in [:terminal_text, :plain_text, :markdown] do
|
||||
[{idx, {type, text, info}} | tail]
|
||||
end
|
||||
|
||||
# Session server keeps all outputs, so we merge consecutive stdouts
|
||||
defp add_output([{idx, {:stdout, text}} | tail], {_idx, {:stdout, cont}}) do
|
||||
[{idx, {:stdout, normalize_stdout(text <> cont)}} | tail]
|
||||
# Session server keeps all outputs, so we merge consecutive chunks
|
||||
defp add_output(
|
||||
[{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
|
||||
|
||||
defp add_output(outputs, output), do: [output | outputs]
|
||||
|
||||
@doc """
|
||||
Normalizes a text chunk coming form the standard output.
|
||||
|
||||
Handles CR rawinds and caps output lines.
|
||||
"""
|
||||
@spec normalize_stdout(String.t()) :: String.t()
|
||||
def normalize_stdout(text) do
|
||||
text
|
||||
|> Livebook.Utils.apply_rewind()
|
||||
|> Livebook.Utils.cap_lines(max_stdout_lines())
|
||||
defp merge_chunk_outputs(outputs) do
|
||||
outputs
|
||||
|> Enum.reverse()
|
||||
|> Enum.reduce([], &add_output(&2, &1))
|
||||
end
|
||||
|
||||
@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 """
|
||||
Recursively adds index to all outputs, including frames.
|
||||
|
@ -803,60 +850,65 @@ defmodule Livebook.Notebook do
|
|||
@spec prune_cell_outputs(t()) :: t()
|
||||
def prune_cell_outputs(notebook) do
|
||||
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
|
||||
end)
|
||||
end
|
||||
|
||||
defp prune_outputs(outputs) do
|
||||
defp prune_outputs(outputs, appendable?) do
|
||||
outputs
|
||||
|> Enum.reverse()
|
||||
|> do_prune_outputs([])
|
||||
|> do_prune_outputs(appendable?, [])
|
||||
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
|
||||
defp do_prune_outputs([{idx, {:stdout, _}}], acc) do
|
||||
[{idx, {:stdout, :__pruned__}} | acc]
|
||||
# Keep trailing outputs that can be merged with subsequent outputs
|
||||
defp do_prune_outputs([{idx, {type, _, %{chunk: true} = info}}], true = _appendable?, acc)
|
||||
when type in [:terminal_text, :plain_text, :markdown] do
|
||||
[{idx, {type, :__pruned__, info}} | acc]
|
||||
end
|
||||
|
||||
# Keep frame and its relevant contents
|
||||
defp do_prune_outputs([{idx, {:frame, frame_outputs, info}} | outputs], acc) do
|
||||
do_prune_outputs(outputs, [{idx, {:frame, prune_outputs(frame_outputs), info}} | acc])
|
||||
defp do_prune_outputs([{idx, {:frame, frame_outputs, info}} | outputs], appendable?, acc) do
|
||||
do_prune_outputs(
|
||||
outputs,
|
||||
appendable?,
|
||||
[{idx, {:frame, prune_outputs(frame_outputs, true), info}} | acc]
|
||||
)
|
||||
end
|
||||
|
||||
# Keep layout output and its relevant contents
|
||||
defp do_prune_outputs([{idx, {:tabs, tabs_outputs, info}} | outputs], acc) do
|
||||
case prune_outputs(tabs_outputs) do
|
||||
defp do_prune_outputs([{idx, {:tabs, tabs_outputs, info}} | outputs], appendable?, acc) do
|
||||
case prune_outputs(tabs_outputs, false) do
|
||||
[] ->
|
||||
do_prune_outputs(outputs, acc)
|
||||
do_prune_outputs(outputs, appendable?, acc)
|
||||
|
||||
pruned_tabs_outputs ->
|
||||
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
|
||||
|
||||
defp do_prune_outputs([{idx, {:grid, grid_outputs, info}} | outputs], acc) do
|
||||
case prune_outputs(grid_outputs) do
|
||||
defp do_prune_outputs([{idx, {:grid, grid_outputs, info}} | outputs], appendable?, acc) do
|
||||
case prune_outputs(grid_outputs, false) do
|
||||
[] ->
|
||||
do_prune_outputs(outputs, acc)
|
||||
do_prune_outputs(outputs, appendable?, acc)
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
do_prune_outputs(outputs, [{idx, output} | acc])
|
||||
do_prune_outputs(outputs, appendable?, [{idx, output} | acc])
|
||||
end
|
||||
|
||||
# Remove everything else
|
||||
defp do_prune_outputs([_output | outputs], acc) do
|
||||
do_prune_outputs(outputs, acc)
|
||||
defp do_prune_outputs([_output | outputs], appendable?, acc) do
|
||||
do_prune_outputs(outputs, appendable?, acc)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -72,14 +72,12 @@ defprotocol Livebook.Runtime do
|
|||
"""
|
||||
@type output ::
|
||||
:ignored
|
||||
# IO output, adjacent such outputs are treated as a whole
|
||||
| {:stdout, binary()}
|
||||
# Standalone text block otherwise matching :stdout
|
||||
| {:text, binary()}
|
||||
# Text with terminal style and ANSI support
|
||||
| {:terminal_text, text :: String.t(), info :: map()}
|
||||
# Plain text content
|
||||
| {:plain_text, binary()}
|
||||
| {:plain_text, text :: String.t(), info :: map()}
|
||||
# Markdown content
|
||||
| {:markdown, binary()}
|
||||
| {:markdown, text :: String.t(), info :: map()}
|
||||
# A raw image in the given format
|
||||
| {:image, content :: binary(), mime_type :: binary()}
|
||||
# JavaScript powered output
|
||||
|
|
|
@ -76,7 +76,7 @@ defmodule Livebook.Runtime.Evaluator.Formatter do
|
|||
defp to_inspect_output(value, opts \\ []) do
|
||||
try do
|
||||
inspected = inspect(value, inspect_opts(opts))
|
||||
{:text, inspected}
|
||||
{:terminal_text, inspected, %{chunk: false}}
|
||||
catch
|
||||
kind, error ->
|
||||
formatted = format_error(kind, error, __STACKTRACE__)
|
||||
|
@ -172,6 +172,6 @@ defmodule Livebook.Runtime.Evaluator.Formatter do
|
|||
|
||||
defp erlang_to_output(value) do
|
||||
text = :io_lib.format("~p", [value]) |> IO.iodata_to_binary()
|
||||
{:text, text}
|
||||
{:terminal_text, text, %{chunk: false}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -438,7 +438,10 @@ defmodule Livebook.Runtime.Evaluator.IOProxy do
|
|||
string = state.buffer |> Enum.reverse() |> Enum.join()
|
||||
|
||||
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
|
||||
|
||||
%{state | buffer: []}
|
||||
|
|
|
@ -37,40 +37,26 @@ defmodule LivebookWeb.Output do
|
|||
"""
|
||||
end
|
||||
|
||||
defp border?({:stdout, _text}), do: true
|
||||
defp border?({:text, _text}), do: true
|
||||
defp border?({:terminal_text, _text, _info}), do: true
|
||||
defp border?({:plain_text, _text, _info}), do: true
|
||||
defp border?({:error, _message, {:interrupt, _, _}}), do: false
|
||||
defp border?({:error, _message, _type}), do: true
|
||||
defp border?({:grid, _, info}), do: Map.get(info, :boxed, 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)
|
||||
live_component(Output.StdoutComponent, id: id, text: text)
|
||||
live_component(Output.TerminalTextComponent, id: id, text: text)
|
||||
end
|
||||
|
||||
defp render_output({:text, text}, %{id: id}) do
|
||||
assigns = %{id: id, text: text}
|
||||
|
||||
~H"""
|
||||
<Output.TextComponent.render id={@id} content={@text} />
|
||||
"""
|
||||
defp render_output({:plain_text, text, _info}, %{id: id}) do
|
||||
text = if(text == :__pruned__, do: nil, else: text)
|
||||
live_component(Output.PlainTextComponent, id: id, text: text)
|
||||
end
|
||||
|
||||
defp render_output({:plain_text, text}, %{id: id}) do
|
||||
assigns = %{id: 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
|
||||
)
|
||||
defp render_output({:markdown, text, _info}, %{id: id, session_id: session_id}) do
|
||||
text = if(text == :__pruned__, do: nil, else: text)
|
||||
live_component(Output.MarkdownComponent, id: id, session_id: session_id, text: text)
|
||||
end
|
||||
|
||||
defp render_output({:image, content, mime_type}, %{id: id}) do
|
||||
|
|
|
@ -3,30 +3,41 @@ defmodule LivebookWeb.Output.MarkdownComponent do
|
|||
|
||||
@impl true
|
||||
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
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
{text, assigns} = Map.pop(assigns, :text)
|
||||
socket = assign(socket, assigns)
|
||||
|
||||
{:ok,
|
||||
push_event(socket, "markdown_renderer:#{socket.assigns.id}:content", %{
|
||||
content: socket.assigns.content
|
||||
})}
|
||||
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
|
||||
class="markdown"
|
||||
id={"markdown-renderer-#{@id}"}
|
||||
phx-hook="MarkdownRenderer"
|
||||
data-id={@id}
|
||||
data-session-path={~p"/sessions/#{@session_id}"}
|
||||
data-base-path={~p"/sessions/#{@session_id}"}
|
||||
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>
|
||||
"""
|
||||
end
|
||||
|
|
33
lib/livebook_web/live/output/plain_text_component.ex
Normal file
33
lib/livebook_web/live/output/plain_text_component.ex
Normal 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
|
|
@ -1,4 +1,4 @@
|
|||
defmodule LivebookWeb.Output.StdoutComponent do
|
||||
defmodule LivebookWeb.Output.TerminalTextComponent do
|
||||
use LivebookWeb, :live_component
|
||||
|
||||
@impl true
|
||||
|
@ -15,7 +15,7 @@ defmodule LivebookWeb.Output.StdoutComponent do
|
|||
if text do
|
||||
text = (socket.assigns.last_line || "") <> text
|
||||
|
||||
text = Livebook.Notebook.normalize_stdout(text)
|
||||
text = Livebook.Notebook.normalize_terminal_text(text)
|
||||
|
||||
last_line =
|
||||
case Livebook.Utils.split_at_last_occurrence(text, "\n") do
|
||||
|
@ -49,7 +49,7 @@ defmodule LivebookWeb.Output.StdoutComponent do
|
|||
phx-hook="VirtualizedLines"
|
||||
data-max-height="300"
|
||||
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"
|
||||
>
|
||||
<% # Note 1: We add a newline to each element, so that multiple lines can be copied properly as element.textContent %>
|
|
@ -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
|
|
@ -2783,11 +2783,19 @@ defmodule LivebookWeb.SessionLive do
|
|||
|
||||
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
|
||||
case Notebook.fetch_cell_and_section(prev_data.notebook, cell_id) do
|
||||
{:ok, %{outputs: [{idx, {:stdout, _}} | _]}, _section} ->
|
||||
send_update(LivebookWeb.Output.StdoutComponent, id: "output-#{idx}", text: text)
|
||||
{:ok, %{outputs: [{idx, {^type, _, %{chunk: true}}} | _]}, _section} ->
|
||||
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
|
||||
|
||||
_ ->
|
||||
|
|
|
@ -577,7 +577,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
|
|||
IO.puts("hey")\
|
||||
""",
|
||||
outputs: [
|
||||
{0, {:stdout, "hey"}}
|
||||
{0, {:terminal_text, "hey", %{chunk: true}}}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -614,7 +614,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
|
|||
| source: """
|
||||
IO.puts("hey")\
|
||||
""",
|
||||
outputs: [{0, {:stdout, "hey"}}]
|
||||
outputs: [{0, {:terminal_text, "hey", %{chunk: true}}}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -656,7 +656,10 @@ defmodule Livebook.LiveMarkdown.ExportTest do
|
|||
| source: """
|
||||
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,
|
||||
[
|
||||
{1, {:markdown, "a"}},
|
||||
{2, {:text, "b"}},
|
||||
{3, {:text, "c"}}
|
||||
{2, {:terminal_text, "b", %{chunk: false}}},
|
||||
{3, {:terminal_text, "c", %{chunk: false}}}
|
||||
], %{labels: ["A", "B", "C"]}}}
|
||||
]
|
||||
}
|
||||
|
@ -994,9 +997,9 @@ defmodule Livebook.LiveMarkdown.ExportTest do
|
|||
{0,
|
||||
{:grid,
|
||||
[
|
||||
{1, {:text, "a"}},
|
||||
{1, {:terminal_text, "a", %{chunk: false}}},
|
||||
{2, {:markdown, "b"}},
|
||||
{3, {:text, "c"}}
|
||||
{3, {:terminal_text, "c", %{chunk: false}}}
|
||||
], %{columns: 2}}}
|
||||
]
|
||||
}
|
||||
|
@ -1047,7 +1050,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
|
|||
| source: """
|
||||
IO.puts("hey")\
|
||||
""",
|
||||
outputs: [{0, {:stdout, "hey"}}]
|
||||
outputs: [{0, {:terminal_text, "hey", %{chunk: true}}}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1092,7 +1095,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
|
|||
| source: """
|
||||
IO.puts("hey")\
|
||||
""",
|
||||
outputs: [{0, {:stdout, "hey"}}]
|
||||
outputs: [{0, {:terminal_text, "hey", %{chunk: true}}}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -642,7 +642,10 @@ defmodule Livebook.LiveMarkdown.ImportTest do
|
|||
source: """
|
||||
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: """
|
||||
IO.puts("hey")\
|
||||
""",
|
||||
outputs: [{0, {:text, ":ok"}}, {1, {:text, "hey"}}]
|
||||
outputs: [
|
||||
{0, {:terminal_text, ":ok", %{chunk: false}}},
|
||||
{1, {:terminal_text, "hey", %{chunk: false}}}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -282,7 +282,7 @@ defmodule Livebook.NotebookTest do
|
|||
end
|
||||
|
||||
describe "add_cell_output/3" do
|
||||
test "merges consecutive stdout results" do
|
||||
test "merges consecutive text outputs when both are chunks" do
|
||||
notebook = %{
|
||||
Notebook.new()
|
||||
| sections: [
|
||||
|
@ -290,7 +290,11 @@ defmodule Livebook.NotebookTest do
|
|||
Section.new()
|
||||
| id: "s1",
|
||||
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 %{
|
||||
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
|
||||
|
||||
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.new()
|
||||
| sections: [
|
||||
|
@ -324,13 +373,18 @@ defmodule Livebook.NotebookTest do
|
|||
assert %{
|
||||
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
|
||||
|
||||
test "normalizes consecutive stdout results to respect CR" do
|
||||
test "normalizes consecutive terminal text outputs to respect CR" do
|
||||
notebook = %{
|
||||
Notebook.new()
|
||||
| sections: [
|
||||
|
@ -338,7 +392,11 @@ defmodule Livebook.NotebookTest do
|
|||
Section.new()
|
||||
| id: "s1",
|
||||
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 %{
|
||||
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
|
||||
|
||||
test "updates existing frames on frame update output" do
|
||||
|
@ -384,12 +447,16 @@ defmodule Livebook.NotebookTest do
|
|||
cells: [
|
||||
%{
|
||||
outputs: [
|
||||
{0, {:frame, [{2, {:text, "hola"}}], %{ref: "1", type: :default}}}
|
||||
{0,
|
||||
{:frame, [{2, {:terminal_text, "hola", %{chunk: false}}}],
|
||||
%{ref: "1", type: :default}}}
|
||||
]
|
||||
},
|
||||
%{
|
||||
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,
|
||||
"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
|
||||
|
||||
|
@ -424,6 +531,36 @@ defmodule Livebook.NotebookTest do
|
|||
]
|
||||
} = Notebook.add_cell_output(notebook, "c1", :ignored)
|
||||
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
|
||||
|
||||
describe "find_frame_outputs/2" do
|
||||
|
|
|
@ -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"
|
||||
end
|
||||
|
@ -77,7 +77,9 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServerTest do
|
|||
|
||||
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"
|
||||
end
|
||||
|
||||
|
@ -87,7 +89,7 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServerTest do
|
|||
|
||||
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}}
|
||||
end
|
||||
|
||||
|
|
|
@ -18,17 +18,17 @@ defmodule Livebook.Runtime.Evaluator.IOProxyTest do
|
|||
describe ":stdio interoperability" do
|
||||
test "IO.puts", %{io: io} do
|
||||
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
|
||||
|
||||
test "IO.write", %{io: io} do
|
||||
IO.write(io, "hey")
|
||||
assert_receive {:runtime_evaluation_output, :ref, {:stdout, "hey"}}
|
||||
assert_receive {:runtime_evaluation_output, :ref, {:terminal_text, "hey", %{chunk: true}}}
|
||||
end
|
||||
|
||||
test "IO.inspect", %{io: io} do
|
||||
IO.inspect(io, %{}, [])
|
||||
assert_receive {:runtime_evaluation_output, :ref, {:stdout, "%{}\n"}}
|
||||
assert_receive {:runtime_evaluation_output, :ref, {:terminal_text, "%{}\n", %{chunk: true}}}
|
||||
end
|
||||
|
||||
test "IO.read", %{io: io} do
|
||||
|
@ -83,31 +83,37 @@ defmodule Livebook.Runtime.Evaluator.IOProxyTest do
|
|||
test "buffers rapid output", %{io: io} do
|
||||
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
|
||||
|
||||
test "respects CR as line cleaner", %{io: io} do
|
||||
IO.write(io, "hey")
|
||||
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
|
||||
|
||||
test "after_evaluation/1 synchronously sends buffer contents", %{io: io} do
|
||||
IO.puts(io, "hey")
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
describe "token requests" do
|
||||
|
|
|
@ -49,8 +49,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
|
|||
|
||||
Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, [])
|
||||
|
||||
assert_receive {:runtime_evaluation_response, :code_1, {:text, ansi_number(3)},
|
||||
metadata() = metadata}
|
||||
assert_receive {:runtime_evaluation_response, :code_1,
|
||||
{:terminal_text, ansi_number(3), %{}}, metadata() = metadata}
|
||||
|
||||
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])
|
||||
|
||||
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
|
||||
|
||||
test "given invalid parent ref uses the default context", %{evaluator: evaluator} do
|
||||
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
|
||||
|
||||
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()}
|
||||
|
||||
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])
|
||||
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
|
||||
|
||||
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()}
|
||||
|
||||
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, [])
|
||||
assert_receive {:runtime_evaluation_response, :code_2, {:text, result2}, metadata()}
|
||||
|
||||
assert_receive {:runtime_evaluation_response, :code_2, {:terminal_text, result2, %{}},
|
||||
metadata()}
|
||||
|
||||
assert result1 != result2
|
||||
|
||||
|
@ -114,16 +124,21 @@ defmodule Livebook.Runtime.EvaluatorTest do
|
|||
assert_receive {:runtime_evaluation_response, :code_1, _, metadata()}
|
||||
|
||||
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, [])
|
||||
assert_receive {:runtime_evaluation_response, :code_2, {:text, ^result2}, metadata()}
|
||||
|
||||
assert_receive {:runtime_evaluation_response, :code_2, {:terminal_text, ^result2, %{}},
|
||||
metadata()}
|
||||
end
|
||||
|
||||
test "captures standard output and sends it to the caller", %{evaluator: evaluator} do
|
||||
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
|
||||
|
||||
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"}
|
||||
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
|
||||
|
||||
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, [])
|
||||
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])
|
||||
|
||||
assert_receive {:runtime_evaluation_response, :code_2, {:error, _, _}, metadata()}
|
||||
|
||||
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
|
||||
|
||||
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"]
|
||||
Evaluator.evaluate_code(evaluator, :elixir, code, :code_1, [], opts)
|
||||
|
||||
assert_receive {:runtime_evaluation_response, :code_1, {:text, ansi_string("/path/dir")},
|
||||
metadata()}
|
||||
assert_receive {:runtime_evaluation_response, :code_1,
|
||||
{:terminal_text, ansi_string("/path/dir"), %{}}, metadata()}
|
||||
end
|
||||
|
||||
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, [])
|
||||
|
||||
assert_receive {:runtime_evaluation_response, :code_1, {:text, widget_pid1_string},
|
||||
metadata()}
|
||||
assert_receive {:runtime_evaluation_response, :code_1,
|
||||
{:terminal_text, widget_pid1_string, %{}}, metadata()}
|
||||
|
||||
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, [])
|
||||
|
||||
assert_receive {:runtime_evaluation_response, :code_1, {:text, widget_pid2_string},
|
||||
metadata()}
|
||||
assert_receive {:runtime_evaluation_response, :code_1,
|
||||
{:terminal_text, widget_pid2_string, %{}}, metadata()}
|
||||
|
||||
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},
|
||||
metadata()}
|
||||
assert_receive {:runtime_evaluation_response, :code_1,
|
||||
{:terminal_text, widget_pid1_string, %{}}, metadata()}
|
||||
|
||||
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, [])
|
||||
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
|
||||
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")
|
||||
|
||||
|
@ -418,7 +438,7 @@ defmodule Livebook.Runtime.EvaluatorTest do
|
|||
"""
|
||||
|
||||
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"))
|
||||
|
||||
|
@ -722,7 +742,7 @@ defmodule Livebook.Runtime.EvaluatorTest do
|
|||
ref = eval_idx
|
||||
parent_refs = Enum.to_list((eval_idx - 1)..0//-1)
|
||||
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}
|
||||
end
|
||||
|
||||
|
@ -1138,8 +1158,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
|
|||
test "kills widgets that no evaluation points to", %{evaluator: evaluator} do
|
||||
Evaluator.evaluate_code(evaluator, :elixir, spawn_widget_code(), :code_1, [])
|
||||
|
||||
assert_receive {:runtime_evaluation_response, :code_1, {:text, widget_pid1_string},
|
||||
metadata()}
|
||||
assert_receive {:runtime_evaluation_response, :code_1,
|
||||
{:terminal_text, widget_pid1_string, %{}}, metadata()}
|
||||
|
||||
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, [])
|
||||
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)
|
||||
|
||||
# Define the module in a different evaluation
|
||||
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
|
||||
|
||||
|
@ -1185,7 +1205,9 @@ defmodule Livebook.Runtime.EvaluatorTest do
|
|||
Evaluator.initialize_from(evaluator, parent_evaluator, [:code_1])
|
||||
|
||||
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
|
||||
|
||||
|
@ -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
|
||||
|
||||
test "mixed erlang/elixir bindings", %{evaluator: evaluator} do
|
||||
|
@ -1242,7 +1265,8 @@ defmodule Livebook.Runtime.EvaluatorTest do
|
|||
code = ~S"#{x=>1}."
|
||||
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
|
||||
|
||||
test "does not return error marker on empty source", %{evaluator: evaluator} do
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Livebook.Session.DataTest do
|
|||
|
||||
@eval_resp {:ok, [1, 2, 3]}
|
||||
@smart_cell_definitions [%{kind: "text", name: "Text", requirement_presets: []}]
|
||||
@stdout {:terminal_text, "Hello!", %{chunk: true}}
|
||||
@cid "__anonymous__"
|
||||
|
||||
defp eval_meta(opts \\ []) do
|
||||
|
@ -78,13 +79,13 @@ defmodule Livebook.Session.DataTest do
|
|||
Notebook.Section.new()
|
||||
| id: "s1",
|
||||
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)
|
||||
|
||||
assert %{notebook: %{sections: [%{cells: [%{outputs: []}]}]}} =
|
||||
|
@ -1819,14 +1820,14 @@ defmodule Livebook.Session.DataTest do
|
|||
{:queue_cells_evaluation, @cid, ["c1"]}
|
||||
])
|
||||
|
||||
operation = {:add_cell_evaluation_output, @cid, "c1", {:stdout, "Hello!"}}
|
||||
operation = {:add_cell_evaluation_output, @cid, "c1", @stdout}
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
notebook: %{
|
||||
sections: [
|
||||
%{
|
||||
cells: [%{outputs: [{1, {:stdout, "Hello!"}}]}]
|
||||
cells: [%{outputs: [{1, @stdout}]}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1842,14 +1843,14 @@ defmodule Livebook.Session.DataTest do
|
|||
evaluate_cells_operations(["setup", "c1"])
|
||||
])
|
||||
|
||||
operation = {:add_cell_evaluation_output, @cid, "c1", {:stdout, "Hello!"}}
|
||||
operation = {:add_cell_evaluation_output, @cid, "c1", @stdout}
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
notebook: %{
|
||||
sections: [
|
||||
%{
|
||||
cells: [%{outputs: [{2, {:stdout, "Hello!"}}, _result]}]
|
||||
cells: [%{outputs: [{2, @stdout}, _result]}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1868,7 +1869,7 @@ defmodule Livebook.Session.DataTest do
|
|||
{: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)
|
||||
end
|
||||
|
|
|
@ -187,7 +187,7 @@ defmodule Livebook.SessionTest do
|
|||
| kind: "text",
|
||||
source: "chunk 1\n\nchunk 2",
|
||||
chunks: [{0, 7}, {9, 7}],
|
||||
outputs: [{1, {:text, "Hello"}}]
|
||||
outputs: [{1, {:terminal_text, "Hello", %{chunk: false}}}]
|
||||
}
|
||||
|
||||
section = %{Notebook.Section.new() | cells: [smart_cell]}
|
||||
|
@ -210,7 +210,7 @@ defmodule Livebook.SessionTest do
|
|||
|
||||
assert_receive {:operation,
|
||||
{: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
|
||||
|
||||
test "doesn't garbage collect input values" do
|
||||
|
@ -920,8 +920,8 @@ defmodule Livebook.SessionTest do
|
|||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
assert_receive {:operation,
|
||||
{:add_cell_evaluation_response, _, ^cell_id, {:text, text_output},
|
||||
%{evaluation_time_ms: _time_ms}}}
|
||||
{:add_cell_evaluation_response, _, ^cell_id,
|
||||
{:terminal_text, text_output, %{}}, %{evaluation_time_ms: _time_ms}}}
|
||||
|
||||
assert text_output =~ "hey"
|
||||
end
|
||||
|
@ -944,8 +944,8 @@ defmodule Livebook.SessionTest do
|
|||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
assert_receive {:operation,
|
||||
{:add_cell_evaluation_response, _, ^cell_id, {:text, text_output},
|
||||
%{evaluation_time_ms: _time_ms}}}
|
||||
{:add_cell_evaluation_response, _, ^cell_id,
|
||||
{:terminal_text, text_output, %{}}, %{evaluation_time_ms: _time_ms}}}
|
||||
|
||||
assert text_output =~ ":error"
|
||||
end
|
||||
|
|
|
@ -207,7 +207,8 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
|
|||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
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"
|
||||
end
|
||||
|
@ -271,7 +272,8 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
|
|||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
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"
|
||||
end
|
||||
|
|
|
@ -221,7 +221,7 @@ defmodule LivebookWeb.SessionControllerTest do
|
|||
| source: """
|
||||
IO.puts("hey")\
|
||||
""",
|
||||
outputs: [{0, {:stdout, "hey"}}]
|
||||
outputs: [{0, {:terminal_text, "hey", %{chunk: true}}}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -81,11 +81,11 @@ defmodule LivebookWeb.AppSessionLiveTest do
|
|||
| cells: [
|
||||
%{
|
||||
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)
|
||||
| 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: [
|
||||
%{
|
||||
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)
|
||||
|
|
|
@ -176,8 +176,8 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
|
||||
|
||||
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
|
||||
|
||||
test "cancelling cell evaluation", %{conn: conn, session: session} do
|
||||
|
@ -627,7 +627,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
end
|
||||
|
||||
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)
|
||||
evaluate_setup(session.pid)
|
||||
|
||||
|
@ -636,14 +636,21 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
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}")
|
||||
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)
|
||||
# Render once, so that stdout send_update is processed
|
||||
# Render once, so that the send_update is processed
|
||||
_ = render(view)
|
||||
assert render(view) =~ "line 2"
|
||||
end
|
||||
|
@ -660,7 +667,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
send(
|
||||
session.pid,
|
||||
{: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}")
|
||||
|
@ -669,7 +676,8 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
send(
|
||||
session.pid,
|
||||
{: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)
|
||||
|
@ -696,11 +704,13 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
send(
|
||||
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,
|
||||
{: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}")
|
||||
refute render(view) =~ "line 1"
|
||||
|
@ -721,11 +731,13 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
send(
|
||||
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,
|
||||
{: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}")
|
||||
refute render(view) =~ "line 1"
|
||||
|
@ -1467,7 +1479,8 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
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"
|
||||
end
|
||||
|
@ -1514,7 +1527,8 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
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"
|
||||
end
|
||||
|
@ -1605,7 +1619,8 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
|
||||
|
||||
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")
|
||||
Settings.set_env_var(attrs)
|
||||
|
@ -1616,7 +1631,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
assert_receive {:operation,
|
||||
{: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"})
|
||||
|
||||
|
@ -1626,7 +1641,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
assert_receive {:operation,
|
||||
{: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")
|
||||
|
||||
|
@ -1635,7 +1650,8 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
|
||||
|
||||
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
|
||||
|
||||
@tag :tmp_dir
|
||||
|
@ -1669,7 +1685,8 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
|
||||
|
||||
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"
|
||||
|
||||
|
@ -1680,7 +1697,8 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
|
||||
|
||||
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"
|
||||
end
|
||||
|
@ -2009,7 +2027,12 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
|
||||
|
||||
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()
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue