mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-07 13:34:55 +08:00
Restore code markers and doctest indicators on refresh (#1949)
This commit is contained in:
parent
f507f67488
commit
04efa5932f
11 changed files with 282 additions and 144 deletions
|
@ -205,4 +205,5 @@ Also some spacing adjustments.
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,6 +253,11 @@ const Cell = {
|
||||||
liveEditor.updateDoctest(doctestReport);
|
liveEditor.updateDoctest(doctestReport);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.handleEvent(`erase_outputs`, () => {
|
||||||
|
liveEditor.setCodeMarkers([]);
|
||||||
|
liveEditor.clearDoctests();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import LiveEditor from "./cell_editor/live_editor";
|
import LiveEditor from "./cell_editor/live_editor";
|
||||||
import { getAttributeOrThrow } from "../lib/attribute";
|
import { getAttributeOrThrow, parseBoolean } from "../lib/attribute";
|
||||||
|
|
||||||
const CellEditor = {
|
const CellEditor = {
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -7,7 +7,7 @@ const CellEditor = {
|
||||||
|
|
||||||
this.handleEvent(
|
this.handleEvent(
|
||||||
`cell_editor_init:${this.props.cellId}:${this.props.tag}`,
|
`cell_editor_init:${this.props.cellId}:${this.props.tag}`,
|
||||||
({ source_view, language, intellisense, read_only }) => {
|
({ source, revision, doctest_reports, code_markers }) => {
|
||||||
const editorContainer = this.el.querySelector(
|
const editorContainer = this.el.querySelector(
|
||||||
`[data-el-editor-container]`
|
`[data-el-editor-container]`
|
||||||
);
|
);
|
||||||
|
@ -20,11 +20,13 @@ const CellEditor = {
|
||||||
editorEl,
|
editorEl,
|
||||||
this.props.cellId,
|
this.props.cellId,
|
||||||
this.props.tag,
|
this.props.tag,
|
||||||
source_view.source,
|
source,
|
||||||
source_view.revision,
|
revision,
|
||||||
language,
|
this.props.language,
|
||||||
intellisense,
|
this.props.intellisense,
|
||||||
read_only
|
this.props.readOnly,
|
||||||
|
code_markers,
|
||||||
|
doctest_reports
|
||||||
);
|
);
|
||||||
|
|
||||||
this.liveEditor.onMount(() => {
|
this.liveEditor.onMount(() => {
|
||||||
|
@ -68,6 +70,13 @@ const CellEditor = {
|
||||||
return {
|
return {
|
||||||
cellId: getAttributeOrThrow(this.el, "data-cell-id"),
|
cellId: getAttributeOrThrow(this.el, "data-cell-id"),
|
||||||
tag: getAttributeOrThrow(this.el, "data-tag"),
|
tag: getAttributeOrThrow(this.el, "data-tag"),
|
||||||
|
language: getAttributeOrThrow(this.el, "data-language"),
|
||||||
|
intellisense: getAttributeOrThrow(
|
||||||
|
this.el,
|
||||||
|
"data-intellisense",
|
||||||
|
parseBoolean
|
||||||
|
),
|
||||||
|
readOnly: getAttributeOrThrow(this.el, "data-read-only", parseBoolean),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,9 @@ class LiveEditor {
|
||||||
revision,
|
revision,
|
||||||
language,
|
language,
|
||||||
intellisense,
|
intellisense,
|
||||||
readOnly
|
readOnly,
|
||||||
|
codeMarkers,
|
||||||
|
doctestReports
|
||||||
) {
|
) {
|
||||||
this.hook = hook;
|
this.hook = hook;
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
@ -38,6 +40,14 @@ class LiveEditor {
|
||||||
this._remoteUserByClientId = {};
|
this._remoteUserByClientId = {};
|
||||||
this._doctestByLine = {};
|
this._doctestByLine = {};
|
||||||
|
|
||||||
|
this._initializeWidgets = () => {
|
||||||
|
this.setCodeMarkers(codeMarkers);
|
||||||
|
|
||||||
|
doctestReports.forEach((doctestReport) => {
|
||||||
|
this.updateDoctest(doctestReport);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const serverAdapter = new HookServerAdapter(hook, cellId, tag);
|
const serverAdapter = new HookServerAdapter(hook, cellId, tag);
|
||||||
this.editorClient = new EditorClient(serverAdapter, revision);
|
this.editorClient = new EditorClient(serverAdapter, revision);
|
||||||
|
|
||||||
|
@ -351,6 +361,9 @@ class LiveEditor {
|
||||||
this.editor._modelData.view._contentWidgets.overflowingContentWidgetsDomNode.domNode.appendChild(
|
this.editor._modelData.view._contentWidgets.overflowingContentWidgetsDomNode.domNode.appendChild(
|
||||||
commandPaletteNode
|
commandPaletteNode
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add the widgets that the editor was initialized with
|
||||||
|
this._initializeWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -80,10 +80,6 @@ class DetailsWidget {
|
||||||
const detailsHtml = details.join("\n");
|
const detailsHtml = details.join("\n");
|
||||||
const numberOfLines = details.length;
|
const numberOfLines = details.length;
|
||||||
|
|
||||||
const marginWidth = this._editor
|
|
||||||
.getDomNode()
|
|
||||||
.querySelector(".margin-view-overlays").offsetWidth;
|
|
||||||
|
|
||||||
const fontSize = this._editor.getOption(
|
const fontSize = this._editor.getOption(
|
||||||
monaco.editor.EditorOption.fontSize
|
monaco.editor.EditorOption.fontSize
|
||||||
);
|
);
|
||||||
|
@ -99,7 +95,6 @@ class DetailsWidget {
|
||||||
"editor-theme-aware-ansi"
|
"editor-theme-aware-ansi"
|
||||||
);
|
);
|
||||||
detailsNode.style.fontSize = `${fontSize}px`;
|
detailsNode.style.fontSize = `${fontSize}px`;
|
||||||
detailsNode.style.paddingLeft = `calc(${marginWidth}px + ${column}ch)`;
|
|
||||||
|
|
||||||
this._overlayWidget = {
|
this._overlayWidget = {
|
||||||
getId: () => `livebook.doctest.overlay.${line}`,
|
getId: () => `livebook.doctest.overlay.${line}`,
|
||||||
|
@ -117,6 +112,12 @@ class DetailsWidget {
|
||||||
domNode: document.createElement("div"),
|
domNode: document.createElement("div"),
|
||||||
onDomNodeTop: (top) => {
|
onDomNodeTop: (top) => {
|
||||||
detailsNode.style.top = `${top}px`;
|
detailsNode.style.top = `${top}px`;
|
||||||
|
|
||||||
|
const marginWidth = this._editor
|
||||||
|
.getDomNode()
|
||||||
|
.querySelector(".margin-view-overlays").offsetWidth;
|
||||||
|
|
||||||
|
detailsNode.style.paddingLeft = `calc(${marginWidth}px + ${column}ch)`;
|
||||||
},
|
},
|
||||||
onComputedHeight: (height) => {
|
onComputedHeight: (height) => {
|
||||||
detailsNode.style.height = `${height}px`;
|
detailsNode.style.height = `${height}px`;
|
||||||
|
|
|
@ -547,16 +547,6 @@ defmodule Livebook.Session.Data do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_operation(data, {:add_cell_doctest_report, _client_id, id, _doctest_report}) do
|
|
||||||
with {:ok, _cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id) do
|
|
||||||
data
|
|
||||||
|> with_actions()
|
|
||||||
|> wrap_ok()
|
|
||||||
else
|
|
||||||
_ -> :error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply_operation(data, {:add_cell_evaluation_output, _client_id, id, output}) do
|
def apply_operation(data, {:add_cell_evaluation_output, _client_id, id, output}) do
|
||||||
with {:ok, cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id) do
|
with {:ok, cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id) do
|
||||||
data
|
data
|
||||||
|
@ -587,6 +577,17 @@ defmodule Livebook.Session.Data do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def apply_operation(data, {:add_cell_doctest_report, _client_id, id, doctest_report}) do
|
||||||
|
with {:ok, cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id) do
|
||||||
|
data
|
||||||
|
|> with_actions()
|
||||||
|
|> add_cell_doctest_report(cell, doctest_report)
|
||||||
|
|> wrap_ok()
|
||||||
|
else
|
||||||
|
_ -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def apply_operation(data, {:bind_input, _client_id, cell_id, input_id}) do
|
def apply_operation(data, {:bind_input, _client_id, cell_id, input_id}) do
|
||||||
with {:ok, cell, _section} <- Notebook.fetch_cell_and_section(data.notebook, cell_id),
|
with {:ok, cell, _section} <- Notebook.fetch_cell_and_section(data.notebook, cell_id),
|
||||||
Cell.evaluable?(cell),
|
Cell.evaluable?(cell),
|
||||||
|
@ -1224,7 +1225,8 @@ defmodule Livebook.Session.Data do
|
||||||
identifiers_used: metadata.identifiers_used,
|
identifiers_used: metadata.identifiers_used,
|
||||||
identifiers_defined: metadata.identifiers_defined,
|
identifiers_defined: metadata.identifiers_defined,
|
||||||
bound_to_input_ids: eval_info.new_bound_to_input_ids,
|
bound_to_input_ids: eval_info.new_bound_to_input_ids,
|
||||||
evaluation_end: DateTime.utc_now()
|
evaluation_end: DateTime.utc_now(),
|
||||||
|
code_markers: metadata.code_markers
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|> update_cell_evaluation_snapshot(cell, section)
|
|> update_cell_evaluation_snapshot(cell, section)
|
||||||
|
@ -1263,6 +1265,14 @@ defmodule Livebook.Session.Data do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp add_cell_doctest_report(data_actions, cell, doctest_report) do
|
||||||
|
data_actions
|
||||||
|
|> update_cell_eval_info!(
|
||||||
|
cell.id,
|
||||||
|
&put_in(&1.doctest_reports[doctest_report.line], doctest_report)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_connect_runtime({data, _} = data_actions, prev_data) do
|
defp maybe_connect_runtime({data, _} = data_actions, prev_data) do
|
||||||
if not Runtime.connected?(data.runtime) and not any_cell_queued?(prev_data) and
|
if not Runtime.connected?(data.runtime) and not any_cell_queued?(prev_data) and
|
||||||
any_cell_queued?(data) do
|
any_cell_queued?(data) do
|
||||||
|
@ -1413,7 +1423,9 @@ defmodule Livebook.Session.Data do
|
||||||
# This is a rough estimate, the exact time is measured in the
|
# This is a rough estimate, the exact time is measured in the
|
||||||
# evaluator itself
|
# evaluator itself
|
||||||
evaluation_start: DateTime.utc_now(),
|
evaluation_start: DateTime.utc_now(),
|
||||||
evaluation_end: nil
|
evaluation_end: nil,
|
||||||
|
code_markers: [],
|
||||||
|
doctest_reports: %{}
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
@ -1609,7 +1621,7 @@ defmodule Livebook.Session.Data do
|
||||||
|> update_every_cell_info(fn
|
|> update_every_cell_info(fn
|
||||||
%{eval: _} = info ->
|
%{eval: _} = info ->
|
||||||
info = update_in(info.eval.outputs_batch_number, &(&1 + 1))
|
info = update_in(info.eval.outputs_batch_number, &(&1 + 1))
|
||||||
put_in(info.eval.validity, :fresh)
|
update_in(info.eval, &%{&1 | validity: :fresh, code_markers: [], doctest_reports: %{}})
|
||||||
|
|
||||||
info ->
|
info ->
|
||||||
info
|
info
|
||||||
|
@ -2025,6 +2037,8 @@ defmodule Livebook.Session.Data do
|
||||||
snapshot: nil,
|
snapshot: nil,
|
||||||
evaluation_snapshot: nil,
|
evaluation_snapshot: nil,
|
||||||
data: nil,
|
data: nil,
|
||||||
|
code_markers: [],
|
||||||
|
doctest_reports: %{},
|
||||||
reevaluates_automatically: false
|
reevaluates_automatically: false
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,7 +51,10 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end)
|
end)
|
||||||
}
|
}
|
||||||
|
|
||||||
push_event(socket, "session_init", payload)
|
socket = push_event(socket, "session_init", payload)
|
||||||
|
|
||||||
|
cells = for {cell, _} <- Notebook.cells_with_section(data.notebook), do: cell
|
||||||
|
push_cell_editor_payloads(socket, data, cells)
|
||||||
else
|
else
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
@ -1720,6 +1723,11 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp after_operation(socket, _prev_socket, {:insert_cell, client_id, _, _, _, cell_id, _attrs}) do
|
defp after_operation(socket, _prev_socket, {:insert_cell, client_id, _, _, _, cell_id, _attrs}) do
|
||||||
|
{:ok, cell, _section} =
|
||||||
|
Notebook.fetch_cell_and_section(socket.private.data.notebook, cell_id)
|
||||||
|
|
||||||
|
socket = push_cell_editor_payloads(socket, socket.private.data, [cell])
|
||||||
|
|
||||||
socket = prune_cell_sources(socket)
|
socket = prune_cell_sources(socket)
|
||||||
|
|
||||||
if client_id == socket.assigns.client_id do
|
if client_id == socket.assigns.client_id do
|
||||||
|
@ -1747,6 +1755,11 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp after_operation(socket, _prev_socket, {:restore_cell, client_id, cell_id}) do
|
defp after_operation(socket, _prev_socket, {:restore_cell, client_id, cell_id}) do
|
||||||
|
{:ok, cell, _section} =
|
||||||
|
Notebook.fetch_cell_and_section(socket.private.data.notebook, cell_id)
|
||||||
|
|
||||||
|
socket = push_cell_editor_payloads(socket, socket.private.data, [cell])
|
||||||
|
|
||||||
socket = prune_cell_sources(socket)
|
socket = prune_cell_sources(socket)
|
||||||
|
|
||||||
if client_id == socket.assigns.client_id do
|
if client_id == socket.assigns.client_id do
|
||||||
|
@ -1795,14 +1808,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
_prev_socket,
|
_prev_socket,
|
||||||
{:add_cell_doctest_report, _client_id, cell_id, doctest_report}
|
{:add_cell_doctest_report, _client_id, cell_id, doctest_report}
|
||||||
) do
|
) do
|
||||||
doctest_report =
|
push_event(socket, "doctest_report:#{cell_id}", doctest_report_payload(doctest_report))
|
||||||
Map.replace_lazy(doctest_report, :details, fn details ->
|
|
||||||
details
|
|
||||||
|> LivebookWeb.Helpers.ANSI.ansi_string_to_html_lines()
|
|
||||||
|> Enum.map(&Phoenix.HTML.safe_to_string/1)
|
|
||||||
end)
|
|
||||||
|
|
||||||
push_event(socket, "doctest_report:#{cell_id}", doctest_report)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp after_operation(
|
defp after_operation(
|
||||||
|
@ -1813,6 +1819,10 @@ defmodule LivebookWeb.SessionLive do
|
||||||
prune_cell_sources(socket)
|
prune_cell_sources(socket)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp after_operation(socket, _prev_socket, {:erase_outputs, _client_id}) do
|
||||||
|
push_event(socket, "erase_outputs", %{})
|
||||||
|
end
|
||||||
|
|
||||||
defp after_operation(socket, prev_socket, {:set_deployed_app_slug, _client_id, slug}) do
|
defp after_operation(socket, prev_socket, {:set_deployed_app_slug, _client_id, slug}) do
|
||||||
prev_slug = prev_socket.private.data.deployed_app_slug
|
prev_slug = prev_socket.private.data.deployed_app_slug
|
||||||
|
|
||||||
|
@ -2041,6 +2051,74 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp push_cell_editor_payloads(socket, data, cells) do
|
||||||
|
for cell <- cells,
|
||||||
|
{tag, payload} <- cell_editor_init_payloads(cell, data.cell_infos[cell.id]),
|
||||||
|
reduce: socket do
|
||||||
|
socket ->
|
||||||
|
push_event(socket, "cell_editor_init:#{cell.id}:#{tag}", payload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cell_editor_init_payloads(%Cell.Code{} = cell, cell_info) do
|
||||||
|
[
|
||||||
|
primary: %{
|
||||||
|
source: cell.source,
|
||||||
|
revision: cell_info.sources.primary.revision,
|
||||||
|
code_markers: cell_info.eval.code_markers,
|
||||||
|
doctest_reports:
|
||||||
|
for {_, doctest_report} <- cell_info.eval.doctest_reports do
|
||||||
|
doctest_report_payload(doctest_report)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cell_editor_init_payloads(%Cell.Markdown{} = cell, cell_info) do
|
||||||
|
[
|
||||||
|
primary: %{
|
||||||
|
source: cell.source,
|
||||||
|
revision: cell_info.sources.primary.revision,
|
||||||
|
code_markers: [],
|
||||||
|
doctest_reports: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cell_editor_init_payloads(%Cell.Smart{} = cell, cell_info) do
|
||||||
|
[
|
||||||
|
primary: %{
|
||||||
|
source: cell.source,
|
||||||
|
revision: cell_info.sources.primary.revision,
|
||||||
|
code_markers: cell_info.eval.code_markers,
|
||||||
|
doctest_reports:
|
||||||
|
for {_, doctest_report} <- cell_info.eval.doctest_reports do
|
||||||
|
doctest_report_payload(doctest_report)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
] ++
|
||||||
|
if cell.editor do
|
||||||
|
[
|
||||||
|
secondary: %{
|
||||||
|
source: cell.editor.source,
|
||||||
|
revision: cell_info.sources.secondary.revision,
|
||||||
|
code_markers: [],
|
||||||
|
doctest_reports: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp doctest_report_payload(doctest_report) do
|
||||||
|
Map.replace_lazy(doctest_report, :details, fn details ->
|
||||||
|
details
|
||||||
|
|> LivebookWeb.Helpers.ANSI.ansi_string_to_html_lines()
|
||||||
|
|> Enum.map(&Phoenix.HTML.safe_to_string/1)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
# Builds view-specific structure of data by cherry-picking
|
# Builds view-specific structure of data by cherry-picking
|
||||||
# only the relevant attributes.
|
# only the relevant attributes.
|
||||||
# We then use `@data_view` in the templates and consequently
|
# We then use `@data_view` in the templates and consequently
|
||||||
|
@ -2149,13 +2227,11 @@ defmodule LivebookWeb.SessionLive do
|
||||||
%{id: section.id, name: section.name}
|
%{id: section.id, name: section.name}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp cell_to_view(%Cell.Markdown{} = cell, data) do
|
defp cell_to_view(%Cell.Markdown{} = cell, _data) do
|
||||||
info = data.cell_infos[cell.id]
|
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: cell.id,
|
id: cell.id,
|
||||||
type: :markdown,
|
type: :markdown,
|
||||||
source_view: source_view(cell.source, info.sources.primary)
|
empty: cell.source == ""
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2165,7 +2241,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
%{
|
%{
|
||||||
id: cell.id,
|
id: cell.id,
|
||||||
type: :code,
|
type: :code,
|
||||||
source_view: source_view(cell.source, info.sources.primary),
|
empty: cell.source == "",
|
||||||
eval: eval_info_to_view(cell, info.eval, data),
|
eval: eval_info_to_view(cell, info.eval, data),
|
||||||
reevaluate_automatically: cell.reevaluate_automatically
|
reevaluate_automatically: cell.reevaluate_automatically
|
||||||
}
|
}
|
||||||
|
@ -2177,16 +2253,16 @@ defmodule LivebookWeb.SessionLive do
|
||||||
%{
|
%{
|
||||||
id: cell.id,
|
id: cell.id,
|
||||||
type: :smart,
|
type: :smart,
|
||||||
source_view: source_view(cell.source, info.sources.primary),
|
empty: cell.source == "",
|
||||||
eval: eval_info_to_view(cell, info.eval, data),
|
eval: eval_info_to_view(cell, info.eval, data),
|
||||||
status: info.status,
|
status: info.status,
|
||||||
js_view: cell.js_view,
|
js_view: cell.js_view,
|
||||||
editor:
|
editor:
|
||||||
cell.editor &&
|
cell.editor &&
|
||||||
%{
|
%{
|
||||||
|
empty: cell.editor.souruce == "",
|
||||||
language: cell.editor.language,
|
language: cell.editor.language,
|
||||||
placement: cell.editor.placement,
|
placement: cell.editor.placement
|
||||||
source_view: source_view(cell.editor.source, info.sources.secondary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -2207,17 +2283,6 @@ defmodule LivebookWeb.SessionLive do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp source_view(:__pruned__, _source_info) do
|
|
||||||
:__pruned__
|
|
||||||
end
|
|
||||||
|
|
||||||
defp source_view(source, source_info) do
|
|
||||||
%{
|
|
||||||
source: source,
|
|
||||||
revision: source_info.revision
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp input_values_for_cell(cell, data) do
|
defp input_values_for_cell(cell, data) do
|
||||||
input_ids =
|
input_ids =
|
||||||
for output <- cell.outputs,
|
for output <- cell.outputs,
|
||||||
|
|
|
@ -16,7 +16,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
||||||
data-evaluation-digest={get_in(@cell_view, [:eval, :evaluation_digest])}
|
data-evaluation-digest={get_in(@cell_view, [:eval, :evaluation_digest])}
|
||||||
data-eval-validity={get_in(@cell_view, [:eval, :validity])}
|
data-eval-validity={get_in(@cell_view, [:eval, :validity])}
|
||||||
data-eval-errored={get_in(@cell_view, [:eval, :errored])}
|
data-eval-errored={get_in(@cell_view, [:eval, :errored])}
|
||||||
data-js-empty={empty?(@cell_view.source_view)}
|
data-js-empty={@cell_view.empty}
|
||||||
data-smart-cell-js-view-ref={smart_cell_js_view_ref(@cell_view)}
|
data-smart-cell-js-view-ref={smart_cell_js_view_ref(@cell_view)}
|
||||||
data-allowed-uri-schemes={Enum.join(@allowed_uri_schemes, ",")}
|
data-allowed-uri-schemes={Enum.join(@allowed_uri_schemes, ",")}
|
||||||
>
|
>
|
||||||
|
@ -38,12 +38,10 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
||||||
</.cell_actions>
|
</.cell_actions>
|
||||||
<.cell_body>
|
<.cell_body>
|
||||||
<div class="pb-4" data-el-editor-box>
|
<div class="pb-4" data-el-editor-box>
|
||||||
<.live_component
|
<.cell_editor
|
||||||
module={LivebookWeb.SessionLive.CellEditorComponent}
|
|
||||||
id={"#{@cell_view.id}-primary"}
|
|
||||||
cell_id={@cell_view.id}
|
cell_id={@cell_view.id}
|
||||||
tag="primary"
|
tag="primary"
|
||||||
source_view={@cell_view.source_view}
|
empty={@cell_view.empty}
|
||||||
language="markdown"
|
language="markdown"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,7 +51,7 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
||||||
id={"markdown-container-#{@cell_view.id}"}
|
id={"markdown-container-#{@cell_view.id}"}
|
||||||
phx-update="ignore"
|
phx-update="ignore"
|
||||||
>
|
>
|
||||||
<.content_skeleton empty={empty?(@cell_view.source_view)} />
|
<.content_skeleton empty={@cell_view.empty} />
|
||||||
</div>
|
</div>
|
||||||
</.cell_body>
|
</.cell_body>
|
||||||
"""
|
"""
|
||||||
|
@ -83,12 +81,10 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
||||||
</.cell_actions>
|
</.cell_actions>
|
||||||
<.cell_body>
|
<.cell_body>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<.live_component
|
<.cell_editor
|
||||||
module={LivebookWeb.SessionLive.CellEditorComponent}
|
|
||||||
id={"#{@cell_view.id}-primary"}
|
|
||||||
cell_id={@cell_view.id}
|
cell_id={@cell_view.id}
|
||||||
tag="primary"
|
tag="primary"
|
||||||
source_view={@cell_view.source_view}
|
empty={@cell_view.empty}
|
||||||
language="elixir"
|
language="elixir"
|
||||||
intellisense
|
intellisense
|
||||||
/>
|
/>
|
||||||
|
@ -132,12 +128,10 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
||||||
</div>
|
</div>
|
||||||
<div data-el-editor-box>
|
<div data-el-editor-box>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<.live_component
|
<.cell_editor
|
||||||
module={LivebookWeb.SessionLive.CellEditorComponent}
|
|
||||||
id={"#{@cell_view.id}-primary"}
|
|
||||||
cell_id={@cell_view.id}
|
cell_id={@cell_view.id}
|
||||||
tag="primary"
|
tag="primary"
|
||||||
source_view={@cell_view.source_view}
|
empty={@cell_view.empty}
|
||||||
language="elixir"
|
language="elixir"
|
||||||
intellisense
|
intellisense
|
||||||
/>
|
/>
|
||||||
|
@ -192,13 +186,11 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
||||||
session_id={@session_id}
|
session_id={@session_id}
|
||||||
client_id={@client_id}
|
client_id={@client_id}
|
||||||
/>
|
/>
|
||||||
<.live_component
|
<.cell_editor
|
||||||
:if={@cell_view.editor}
|
:if={@cell_view.editor}
|
||||||
module={LivebookWeb.SessionLive.CellEditorComponent}
|
|
||||||
id={"#{@cell_view.id}-secondary"}
|
|
||||||
cell_id={@cell_view.id}
|
cell_id={@cell_view.id}
|
||||||
tag="secondary"
|
tag="secondary"
|
||||||
source_view={@cell_view.editor.source_view}
|
empty={@cell_view.editor.empty}
|
||||||
language={@cell_view.editor.language}
|
language={@cell_view.editor.language}
|
||||||
rounded={@cell_view.editor.placement}
|
rounded={@cell_view.editor.placement}
|
||||||
/>
|
/>
|
||||||
|
@ -235,12 +227,10 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
||||||
</div>
|
</div>
|
||||||
<div data-el-editor-box>
|
<div data-el-editor-box>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<.live_component
|
<.cell_editor
|
||||||
module={LivebookWeb.SessionLive.CellEditorComponent}
|
|
||||||
id={"#{@cell_view.id}-primary"}
|
|
||||||
cell_id={@cell_view.id}
|
cell_id={@cell_view.id}
|
||||||
tag="primary"
|
tag="primary"
|
||||||
source_view={@cell_view.source_view}
|
empty={@cell_view.empty}
|
||||||
language="elixir"
|
language="elixir"
|
||||||
intellisense
|
intellisense
|
||||||
read_only
|
read_only
|
||||||
|
@ -580,6 +570,39 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attr :cell_id, :string, required: true
|
||||||
|
attr :tag, :string, required: true
|
||||||
|
attr :empty, :boolean, required: true
|
||||||
|
attr :language, :string, required: true
|
||||||
|
attr :intellisense, :boolean, default: false
|
||||||
|
attr :read_only, :boolean, default: false
|
||||||
|
attr :rounded, :atom, default: :both
|
||||||
|
|
||||||
|
defp cell_editor(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div
|
||||||
|
id={"cell-editor-#{@cell_id}-#{@tag}"}
|
||||||
|
phx-update="ignore"
|
||||||
|
phx-hook="CellEditor"
|
||||||
|
data-cell-id={@cell_id}
|
||||||
|
data-tag={@tag}
|
||||||
|
data-language={@language}
|
||||||
|
data-intellisense={to_string(@intellisense)}
|
||||||
|
data-read-only={to_string(@read_only)}
|
||||||
|
>
|
||||||
|
<div class={["py-3 bg-editor", rounded_class(@rounded)]} data-el-editor-container>
|
||||||
|
<div class="px-8" data-el-skeleton>
|
||||||
|
<.content_skeleton bg_class="bg-gray-500" empty={@empty} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp rounded_class(:both), do: "rounded-lg"
|
||||||
|
defp rounded_class(:top), do: "rounded-t-lg"
|
||||||
|
defp rounded_class(:bottom), do: "rounded-b-lg"
|
||||||
|
|
||||||
defp evaluation_outputs(assigns) do
|
defp evaluation_outputs(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div
|
<div
|
||||||
|
@ -601,9 +624,6 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
defp empty?(%{source: ""} = _source_view), do: true
|
|
||||||
defp empty?(_source_view), do: false
|
|
||||||
|
|
||||||
defp cell_status(%{cell_view: %{eval: %{status: :evaluating}}} = assigns) do
|
defp cell_status(%{cell_view: %{eval: %{status: :evaluating}}} = assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.cell_status_indicator variant={:progressing} change_indicator={true}>
|
<.cell_status_indicator variant={:progressing} change_indicator={true}>
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
defmodule LivebookWeb.SessionLive.CellEditorComponent do
|
|
||||||
use LivebookWeb, :live_component
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def mount(socket) do
|
|
||||||
{:ok, assign(socket, initialized: false)}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def update(assigns, socket) do
|
|
||||||
socket =
|
|
||||||
socket
|
|
||||||
|> assign(assigns)
|
|
||||||
|> assign_new(:intellisense, fn -> false end)
|
|
||||||
|> assign_new(:read_only, fn -> false end)
|
|
||||||
|> assign_new(:rounded, fn -> :both end)
|
|
||||||
|
|
||||||
socket =
|
|
||||||
if not connected?(socket) or socket.assigns.initialized do
|
|
||||||
socket
|
|
||||||
else
|
|
||||||
socket
|
|
||||||
|> push_event(
|
|
||||||
"cell_editor_init:#{socket.assigns.cell_id}:#{socket.assigns.tag}",
|
|
||||||
%{
|
|
||||||
source_view: socket.assigns.source_view,
|
|
||||||
language: socket.assigns.language,
|
|
||||||
intellisense: socket.assigns.intellisense,
|
|
||||||
read_only: socket.assigns.read_only
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|> assign(initialized: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:ok, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def render(assigns) do
|
|
||||||
~H"""
|
|
||||||
<div
|
|
||||||
id={"cell-editor-#{@id}"}
|
|
||||||
phx-update="ignore"
|
|
||||||
phx-hook="CellEditor"
|
|
||||||
data-cell-id={@cell_id}
|
|
||||||
data-tag={@tag}
|
|
||||||
>
|
|
||||||
<div class={["py-3 bg-editor", rounded_class(@rounded)]} data-el-editor-container>
|
|
||||||
<div class="px-8" data-el-skeleton>
|
|
||||||
<.content_skeleton bg_class="bg-gray-500" empty={empty?(@source_view)} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
defp empty?(%{source: ""} = _source_view), do: true
|
|
||||||
defp empty?(_source_view), do: false
|
|
||||||
|
|
||||||
defp rounded_class(:both), do: "rounded-lg"
|
|
||||||
defp rounded_class(:top), do: "rounded-t-lg"
|
|
||||||
defp rounded_class(:bottom), do: "rounded-b-lg"
|
|
||||||
end
|
|
|
@ -22,7 +22,8 @@ defmodule Livebook.Session.DataTest do
|
||||||
interrupted: interrupted,
|
interrupted: interrupted,
|
||||||
evaluation_time_ms: 10,
|
evaluation_time_ms: 10,
|
||||||
identifiers_used: uses,
|
identifiers_used: uses,
|
||||||
identifiers_defined: defines
|
identifiers_defined: defines,
|
||||||
|
code_markers: []
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2493,6 +2494,59 @@ defmodule Livebook.Session.DataTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "apply_operation/2 given :add_cell_doctest_report" do
|
||||||
|
test "returns an error given invalid cell id" do
|
||||||
|
data = Data.new()
|
||||||
|
operation = {:add_cell_doctest_report, @cid, "c1", %{status: :running, line: 5}}
|
||||||
|
assert :error = Data.apply_operation(data, operation)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "adds doctest report to cell evaluation info" do
|
||||||
|
data =
|
||||||
|
data_after_operations!([
|
||||||
|
{:insert_section, @cid, 0, "s1"},
|
||||||
|
{:insert_cell, @cid, "s1", 0, :code, "c1", %{}},
|
||||||
|
{:set_runtime, @cid, connected_noop_runtime()},
|
||||||
|
evaluate_cells_operations(["setup"]),
|
||||||
|
{:queue_cells_evaluation, @cid, ["c1"]}
|
||||||
|
])
|
||||||
|
|
||||||
|
doctest_report = %{status: :running, line: 5}
|
||||||
|
|
||||||
|
operation = {:add_cell_doctest_report, @cid, "c1", doctest_report}
|
||||||
|
|
||||||
|
assert {:ok,
|
||||||
|
%{
|
||||||
|
cell_infos: %{
|
||||||
|
"c1" => %{eval: %{doctest_reports: %{5 => ^doctest_report}}}
|
||||||
|
}
|
||||||
|
}, []} = Data.apply_operation(data, operation)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates doctest report by line" do
|
||||||
|
data =
|
||||||
|
data_after_operations!([
|
||||||
|
{:insert_section, @cid, 0, "s1"},
|
||||||
|
{:insert_cell, @cid, "s1", 0, :code, "c1", %{}},
|
||||||
|
{:set_runtime, @cid, connected_noop_runtime()},
|
||||||
|
evaluate_cells_operations(["setup"]),
|
||||||
|
{:queue_cells_evaluation, @cid, ["c1"]},
|
||||||
|
{:add_cell_doctest_report, @cid, "c1", %{status: :running, line: 5}}
|
||||||
|
])
|
||||||
|
|
||||||
|
doctest_report = %{status: :success, line: 5}
|
||||||
|
|
||||||
|
operation = {:add_cell_doctest_report, @cid, "c1", doctest_report}
|
||||||
|
|
||||||
|
assert {:ok,
|
||||||
|
%{
|
||||||
|
cell_infos: %{
|
||||||
|
"c1" => %{eval: %{doctest_reports: %{5 => ^doctest_report}}}
|
||||||
|
}
|
||||||
|
}, []} = Data.apply_operation(data, operation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "apply_operation/2 given :bind_input" do
|
describe "apply_operation/2 given :bind_input" do
|
||||||
test "returns an error given invalid input cell id" do
|
test "returns an error given invalid input cell id" do
|
||||||
data =
|
data =
|
||||||
|
@ -3047,6 +3101,24 @@ defmodule Livebook.Session.DataTest do
|
||||||
}
|
}
|
||||||
}, _actions} = Data.apply_operation(data, operation)
|
}, _actions} = Data.apply_operation(data, operation)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "removes doctest reports" do
|
||||||
|
data =
|
||||||
|
data_after_operations!([
|
||||||
|
{:insert_section, @cid, 0, "s1"},
|
||||||
|
{:insert_cell, @cid, "s1", 0, :code, "c1", %{}},
|
||||||
|
{:set_runtime, @cid, connected_noop_runtime()},
|
||||||
|
evaluate_cells_operations(["setup", "c1"]),
|
||||||
|
{:add_cell_doctest_report, @cid, "c1", %{status: :running, line: 5}}
|
||||||
|
])
|
||||||
|
|
||||||
|
operation = {:erase_outputs, @cid}
|
||||||
|
|
||||||
|
empty_map = %{}
|
||||||
|
|
||||||
|
assert {:ok, %{cell_infos: %{"c1" => %{eval: %{doctest_reports: ^empty_map}}}}, []} =
|
||||||
|
Data.apply_operation(data, operation)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "apply_operation/2 given :set_notebook_name" do
|
describe "apply_operation/2 given :set_notebook_name" do
|
||||||
|
|
|
@ -13,7 +13,8 @@ defmodule Livebook.SessionTest do
|
||||||
interrupted: false,
|
interrupted: false,
|
||||||
evaluation_time_ms: 10,
|
evaluation_time_ms: 10,
|
||||||
identifiers_used: [],
|
identifiers_used: [],
|
||||||
identifiers_defined: %{}
|
identifiers_defined: %{},
|
||||||
|
code_markers: []
|
||||||
}
|
}
|
||||||
|
|
||||||
describe "file_name_for_download/1" do
|
describe "file_name_for_download/1" do
|
||||||
|
|
Loading…
Add table
Reference in a new issue